Cloudflare Pagesで公開したサイトに問い合わせフォームを付けたいとき、画面だけ作ってもメールは届きません。入力を受けるPages Functions、スパム対策のTurnstile、メール送信サービスのResendを分けて設定します。この記事は「Cloudflare Pages 問い合わせフォーム」「Cloudflare Turnstile 設定方法」で調べている、公開作業を担当する人向けです。
01まず構成と役割を決める
フォームを送信すると、ブラウザーからPages Functionsへ入力内容とTurnstileトークンが渡ります。Functions側で必須項目と送信元を検査し、CloudflareのSiteverifyでトークンを検証します。成功した場合に限り、ResendのAPIへメール送信を依頼します。サイトに置くのは公開可能なTurnstile sitekeyだけです。secret keyとResend APIキーをHTMLやフロントエンドJavaScriptへ書いてはいけません。
このサイトでも送信前の必須項目チェックだけに頼らず、サーバー側で同じ内容を検査しています。ブラウザーの検証は利用者の入力を助けますが、不正なリクエストはブラウザーを経由しないためです。
02Cloudflare Pagesのプロジェクトを用意する
Cloudflareダッシュボードの「Workers & Pages」でPagesプロジェクトを選びます。静的サイトなら、ビルド後の出力ディレクトリを正しく指定してください。このサイトではビルド結果をdistに出しています。Git連携か直接アップロードかで公開操作は異なりますが、フォームの受信処理にはPages Functionsを含める必要があります。HTMLだけをアップロードしても送信APIは動きません。
- プロジェクトの公開URLを開き、ページが表示されることを先に確認する。
- Functionsのルートを用意し、フォームの送信先URLを合わせる。
- PreviewとProductionで利用するドメイン・環境変数を分けて確認する。
公開後はフォーム画面だけでなく、送信先APIの応答も確認します。構成が正しくても、送信先URLが違えばメールは届きません。
03Turnstileのウィジェットを作成する
Turnstileの管理画面でウィジェットを作り、対象ホスト名を登録します。発行されるsitekeyはページへ配置する公開値、secret keyはサーバーでトークンを検証する秘密値です。Cloudflare公式ドキュメントは、画面へのウィジェット設置だけでなく、Siteverify APIによるサーバー側検証を必須としています。検証しない構成では「画面にチェックが出た」だけでスパム対策になりません。
- Turnstileで新しいウィジェットを作成し、公開予定のホスト名を登録する。
- sitekeyをフォーム表示側の設定へ渡す。
- secret keyをPagesのSecretとして登録し、Functionsからだけ参照する。
- サーバーでSiteverifyを呼び、失敗・期限切れ・再利用トークンなら送信を止める。
Turnstileのトークンは一回限りで、有効期間があります。送信後の再試行では新しいトークンを取得する実装が必要です。ローカルやPreview環境の試験には、公式のテスト用キーを使うか、許可ホスト名を明示して本番と混同しないようにします。
04Pagesに環境変数とSecretを設定する
Cloudflare Pagesのプロジェクト設定から、Functionsで参照する値を登録します。このサイトの構成では、TURNSTILE_SITE_KEYは公開値、TURNSTILE_SECRETとRESEND_API_KEYは秘密値です。CONTACT_FROMとCONTACT_TOは送信元・送信先、CONTACT_ORIGINSは許可するフォームのオリジンを表します。変数名は実装に合わせて決めるもので、Cloudflareが固定で要求する名前ではありません。
環境変数を追加しただけで稼働中のデプロイに反映されたと決めつけず、ProductionとPreviewの適用先と再デプロイ後の動作を確認してください。実際のAPIキー、ドメイン認証用レコードの値、問い合わせ内容を記事やスクリーンショットに写さないことも大切です。
05Cloudflare Pagesの料金・無料枠はどう見るか
「Cloudflare 料金」という広い検索では、CDN、WAF、Workers、Pagesなど別製品が混ざります。問い合わせフォームを作る人が確認すべきなのは、静的ページの配信、Pagesのビルド上限、Functionsのリクエスト枠、Turnstileの利用条件、そしてResendのメール送信枠です。Cloudflareの公式資料によると、2026年9月時点でPages Freeは月500ビルド、Pages FunctionsのリクエストはWorkersプランの枠に算入されます。Turnstileには無料プランがあり、無料アカウントのウィジェット数などの制限が設定されています。
小規模な会社サイトなら無料枠で試せる場合がありますが、「無料で必ず運用できる」とは言い切れません。送信件数、Preview環境の利用、他のWorkersの利用量、メールサービス側の枠を合わせて見積もります。費用や上限は変更されるため、発注や運用判断の直前にCloudflare PagesのLimits、WorkersのLimits、Turnstile Plans、Resendの料金表を確認してください。
06公開前にテストすること
正常送信だけでは不十分です。空欄、誤ったメール形式、長すぎる入力、同意未チェック、Turnstile失敗、連続送信をそれぞれ試し、サーバーが拒否することを確認します。メールが届かない場合は、ブラウザーのエラー表示、Functionsの応答、Resendの送信履歴、受信側の迷惑メール判定を順番に切り分けます。送信できたと画面に表示するのは、メールサービスが送信要求を受理したときだけにします。
この構成を使うなら、次はResendのドメイン認証と送信元の設定です。メールの送信経路を先に確認してから、実際のフォームを公開すると切り分けが容易になります。
07具体例:正常送信と不正な直接POSTを比べる
架空のフォームに「個人/氏名/返信先メール/相談種別/本文」を設け、利用者がすべて入力してTurnstileを通過した場合を考えます。ブラウザーは入力と一回限りのトークンをFunctionsへ送ります。Functionsは項目の有無、長さ、メール形式、送信元、短時間の連続送信を確認し、Siteverifyでトークンを検証します。検証成功後だけResendへ固定の担当者宛通知を依頼し、受理された場合に成功表示を出します。送信先はフォームの入力欄にせず、サーバー側設定から決めます。
次に攻撃者がフォームを経由せず `/api/contact` へ本文だけを直接POSTしたとします。ブラウザー側の必須チェックは通らないので、Functionsが欠けた項目とトークンを拒否しなければなりません。トークンが古い、再利用された、別ホスト用である場合も送信しません。認証が通っても同じIPや返信先メールから短時間に大量送信されたら、レート制限や送信総量の上限で止めます。Turnstileだけでメール送信費用の上限を保証できるわけではありません。
公開前の記録には「正常1件は受信箱まで到着」「空欄・偽トークン・連打ではResendへ送信しない」「APIやD1が故障したときは成功表示を出さない」を残します。利用者の入力を失わないエラー表示と、連絡用の代替手段も用意すると、誤検知で正常な相談を逃しにくくなります。実際のAPIキーや問い合わせ本文は記事用の画像・ログへ載せないでください。
参考資料
- Cloudflare Pages: Build configuration
- Cloudflare Pages: Bindings and secrets
- Cloudflare Turnstile: Get started
- Cloudflare Pages: Limits
- Cloudflare Workers: Limits
内容は公開時点の公式資料をもとに構成しています。仕様は変更されることがあるため、導入時は最新の公式情報をご確認ください。

