Contact Form 7で送信できない・くるくる止まらない時の対処法

実測 に検証

送信ボタンを押すとぐるぐる回ったまま止まらない。あるいは何も起きない。

対策として「キャッシュを除外する(nonce が期限切れになるから)」という説明が 広く出回っています。現行の Contact Form 7 では、この説明は当てはまりません。 実際にインストールして計測しました(バージョン 6.1.7)。

実測: 匿名の訪問者向けには nonce が出ていない

フォームを設置したページの HTML から、hidden フィールドを全部取り出しました。

_wpcf7                   = 50
_wpcf7_version           = 6.1.7
_wpcf7_locale            = ja
_wpcf7_unit_tag          = wpcf7-f50-p51-o1
_wpcf7_container_post    = 51
_wpcf7_posted_data_hash  = (空)

nonce に相当するフィールドは 1 つもありません。 HTML 全体を nonce で検索しても出現回数は 0 でした。

念のため、nonce を一切付けずに送信エンドポイントへ POST しました。

$ curl -X POST ".../wp-json/contact-form-7/v1/contact-forms/50/feedback" \
    -F "_wpcf7=50" -F "your-name=テスト太郎" -F "your-email=test@lab.local" \
    -F "your-subject=件名テスト" -F "your-message=本文テスト"

HTTP 200
status : mail_sent
message: Thank you for your message. It has been sent.

送信は成功し、メールも届きました。

実装でも確認した

プラグインのコードを見ると、nonce はログイン中のユーザーにだけ付きます。

// contact-form.php
if ( $this->nonce_is_active() and is_user_logged_in() ) { ... }

// submission.php
if ( ! $this->contact_form->nonce_is_active() or ! is_user_logged_in() ) {
    // 検証をスキップする
}

匿名の訪問者には nonce を付けず、検証もしません。 キャッシュされた HTML を使っても、そこに期限切れになるものが無いので壊れません。

これは Contact Form 7 側がキャッシュと共存するために意図的にそうしている 設計です。「キャッシュから除外しないと nonce が切れる」という対策は、 古いバージョンの情報がそのまま残っているものです。

ログイン中のユーザーには nonce が付きます。ただしログインユーザーの ページは通常キャッシュされないため、こちらも実務では問題になりません。

では現行版で何が原因なのか

計測して分かった、実際に壊れる経路は 3 つです。

原因 1: REST API に到達できない(これが「くるくる」の正体)

Contact Form 7 は REST API のエンドポイントに送信します。

/wp-json/contact-form-7/v1/contact-forms/<フォームID>/feedback

セキュリティプラグインや .htaccess/wp-json を塞いでいると、 ここに到達できません。実測です。

ステータスContent-Type
Apache(.htaccess/wp-json を拒否)403text/html
nginx(.htaccess を読まない)400application/json

JavaScript は JSON を期待しているのに HTML を受け取ります。 パースに失敗し、成功も失敗も表示できないため、 送信中の表示(ぐるぐる)が消えません。

送信ボタンの横で回転表示が止まらない

.htaccess で wp-json を塞いだ状態で送信した画面。4 秒待っても送信中の表示のまま、結果のメッセージは出ない

これが「くるくる止まらない」のいちばん多い原因です。 .htaccess を読まない nginx 側では起きないため、 2 台構成で片方だけ送信できないということも起こります。

確認方法は 1 行です。

curl -s -o /dev/null -D - https://example.com/wp-json/ | grep -i content-type

application/json 以外が返っていたら、これが原因です。 → 「返答が正しい JSON レスポンスではありません」

原因 2: JavaScript が読み込まれていない

こちらは逆に、送信自体は成功します。実測しました。

JavaScript を使わず、ブラウザの通常の POST と同じ形でページに送信した結果です。

HTTP 200
「Thank you for your message. It has been sent.」

JS が無くても動きます。Contact Form 7 はサーバー側でも処理できるよう 作られているため、JS が読み込まれていない場合は ページが再読み込みされて結果が表示されるという挙動になります。

つまり症状で区別できます。

症状意味
ぐるぐる回ったまま止まらないJS は動いている。応答を受け取れていない(原因 1)
ページが再読み込みされて結果が出るJS が読み込まれていない(見た目は悪いが動いている)
ボタンを押しても何も起きないJS がエラーで止まっている(原因 3)

「キャッシュプラグインの JS 結合を切ったら直った」という報告は、 多くが原因 3(JS エラー)です。 → CSS が効かない・JS が動かない

原因 3: JavaScript のエラー

他のプラグインが出した JS エラーで、フォームの初期化が止まっている場合です。 ボタンを押しても何も起きません。

ブラウザのコンソールの一番上のエラーを見ます。 jQuery の二重読み込みが典型です。 → プラグインの競合

送信はできているがメールが届かない場合

ここから先はフォームの問題ではありません。画面に「送信しました」と 出ているなら、Contact Form 7 は仕事を終えています。

画面の表示意味
「ありがとうございます。メッセージは送信されました。」wp_mail() は成功した。届かないのは別の層
「メッセージの送信に失敗しました。」wp_mail() が false を返した。WordPress 側の設定

後者でよくある原因は、送信元アドレスが不正な場合です。 WordPress の既定は wordpress@<サーバー名> で、サーバー名にドットが 含まれていないと PHPMailer が送信を中止します(実測済み)。

WordPress からメールが届かない

Flamingo(Contact Form 7 と同じ作者の保存プラグイン)を入れておくと、 送信内容がデータベースに残ります。メールが届かなくても内容を失わずに済み、 「送信自体はできているのか」の切り分けも一目で終わります。

切り分けの順番

# 1. REST が JSON を返すか(「くるくる」の判定)
curl -s -o /dev/null -D - https://example.com/wp-json/ | grep -i content-type

# 2. フォームのあるページに nonce が出ているか(現行版なら 0 件が正常)
curl -s https://example.com/contact/ | grep -c nonce

# 3. 送信エンドポイントに直接 POST してみる
curl -s -X POST "https://example.com/wp-json/contact-form-7/v1/contact-forms/<ID>/feedback" \
  -F "_wpcf7=<ID>" -F "your-name=test" -F "your-email=test@example.com" \
  -F "your-subject=test" -F "your-message=test"
結果原因
1 が text/htmlREST が塞がれている。これが「くるくる」
3 が mail_sent を返すサーバー側は正常。ブラウザ側(JS・キャッシュ)の問題
3 が mail_failed を返すwp_mail() の失敗。メール設定を見る
3 が validation_failed を返す必須項目の指定漏れ。フォーム設定を見る

3 が mail_sent を返すなら、フォームもメールも動いています。 その場合に調べるのはブラウザ側だけです。

キャッシュ対策は不要になったのか

nonce のための除外は不要になりました。ただし キャッシュ関連で別の問題は残ります。

残っている問題対処
JS の結合・最小化でフォームの JS が壊れるその機能だけをオフにする、または対象外に指定する
送信完了ページをキャッシュしてしまう完了ページをキャッシュ対象外にする
古い HTML が残ってフォーム項目が実際と違うフォームを編集したらキャッシュを消す

「とりあえずフォームのページをキャッシュから除外する」は、 今でも副作用の少ない安全策です。ただし理由は nonce ではありません。

計測しなかったこと

キャッシュプラグインを入れての再現はしていません。 nonce が存在しないことがプラグインの出力とソースの両方で確認できたため、 「キャッシュが nonce を古くする」経路は成立しないと判断しました。

JS の結合で壊れる側は、キャッシュプラグインの実装ごとに違うため、 一般化して書けません。そちらは CSS が効かない・JS が動かない の観点で確認してください。

再現手順

wp plugin install contact-form-7 --activate
# 有効化時に「Contact form 1」が自動で作られる
wp post list --post_type=wpcf7_contact_form --fields=ID,post_title

wp post create --post_type=page --post_status=publish \
  --post_title="お問い合わせ" --post_content='[contact-form-7 id="50"]'

# nonce が出ているか(0 件が現行版の正常)
curl -s "<ページURL>" | grep -c nonce

# nonce なしで送信できるか
curl -s -X POST "http://localhost:8080/wp-json/contact-form-7/v1/contact-forms/50/feedback" \
  -F "_wpcf7=50" -F "your-name=t" -F "your-email=t@lab.local" \
  -F "your-subject=s" -F "your-message=m"
# → status: mail_sent

# REST を塞ぐと Apache だけ 403 text/html になる
# .htaccess に RewriteRule ^wp-json/ - [F,L] を(WordPress ブロックより前に)入れる