問い合わせフォームは送れていた。でも通知が来ていなかった

この記事の概要
ブログの問い合わせフォームを見直していて、変なことに気づきました。
フォームに入力して送信すると、「お問い合わせありがとうございます」のページへ移動します。送った内容も Cloudflare の KV に保存されています。
ここまでは動いていました。
でも、私には何の通知も来ません。
誰かが問い合わせを送っても、自分から Cloudflare の管理画面を見に行かない限り気づけない状態でした。
問い合わせなんて、そんなにたくさん来ないとは思います。だからといって、せっかく来た問い合わせに気づかないのはさすがにまずいです。
そこで今回は、フォームにスパム対策を付け、送信内容を保存したあと、メールでも通知するところまで作りました。
途中で Cloudflare のメール機能を使おうとしてやめ、最終的には Resend というメール送信サービスを使っています。
今回作ったもの
先に全体を図にすると、こうなります。
読者がフォームを送る
↓
Turnstileで人間か確認する
↓
問い合わせ内容をCloudflare KVに保存する
↓
Resendから通知メールを送る
↓
Google Workspaceの受信箱に届く
知らない名前がいくつか出てきますが、役割は単純です。
- Turnstile:Cloudflareのスパム対策
- KV:問い合わせ内容を置いておくCloudflare上の保存場所
- Resend:プログラムからメールを送るためのサービス
フォームを表示しているのは Hugo ですが、送信後の処理は Cloudflare Pages Functions で動かしています。
まずフォームにTurnstileを付けた
最初に行ったのはスパム対策です。
Cloudflare Turnstileのwidgetをフォームへ置き、送信されたtokenをPages FunctionからCloudflareへ送り、本物かどうかを確認します。
画面にTurnstileが表示されているだけでは対策になりません。サーバー側でも確認する必要があります。今回は確認結果に加えて、フォーム用の action とブログの hostname が一致するかも見ています。
設定に使う鍵は二つあります。
- site key:ブラウザに表示してよい公開値
- secret key:外へ出してはいけない値
secret keyはCloudflare PagesのSecretに入れました。リポジトリや作業メモには書きません。
ここで早速つまずきました。
CloudflareのAdd Widget画面でブログのHostnameを追加しようとしたら、候補が No results のまま進まなかったのです。最終的には Turnstile Spin という別の作成画面から、同じHostnameを登録できました。
Cloudflareの管理画面は入口がいくつもあって、同じように見える画面でも動きが違うことがあります。何か入力を間違えたと思って、しばらく同じ画面を触っていました。
Turnstileを追加したあとは、本番フォームからテスト送信し、次を確認しました。
- 完了ページへ移動する
- KVに問い合わせが1件保存される
- IPアドレスとTurnstileのtokenはKVに保存されない
これでフォーム自体は動きました。しかし、まだ通知はありません。
最初はCloudflareからメールを送ろうとした
フォームもデータ保存もCloudflareなので、メールもCloudflareにまとめれば簡単そうです。
ところが、Email Sendingの画面にはPaidと表示されていました。
問い合わせフォームの通知は、来ても月に数件だと思います。そのためだけに有料プランへ上げるのもなあ、と思って調べると、検証済みの宛先へ送るだけなら無料でも使えることが分かりました。
では、それでいこう。
と思ったのですが、ここに別の問題がありました。
無料で使うには、Cloudflare Email Routingを有効にする必要があります。そのActivate画面には、ルートドメインのMXレコードをCloudflare用に変更すると表示されていました。
MXを変えるとGoogle Workspaceに影響する
MXレコードは、そのドメイン宛てのメールをどこへ届けるか決める案内板のようなものです。
私のドメインでは、普段のメールをGoogle Workspaceで受け取っています。そのため、MXレコードはすでにGoogleを向いています。
ここでCloudflare Email Routingを有効にすると、その案内板をCloudflare向けに変えることになります。
うまく共存できる設定を調べる手もありますが、やりたいのは問い合わせ通知を1通送ることだけです。普段使っているメールの受信を壊すリスクに見合いません。
通知先メールアドレスの確認までは済ませていましたが、Activateボタンは押さずにやめました。
Cloudflareの公式ドキュメントにも、別のメール事業者を使っている場合はMXレコードが競合する可能性があると書かれています。
画面に出てきたDNS変更を、そのまま承認しなくてよかったです。
Resendなら送信だけを追加できた
次に選んだのがResendです。
2026年9月時点の無料プランは月3,000通、1日100通までです。問い合わせ通知には十分すぎます。まあ、そこまで問い合わせが来るブログになってから心配すればいいですね。
今回はGoogleアカウントでログインするのではなく、サービス連携用のアカウントを別に作りました。
Resendには、普段のメールで使っているルートドメインではなく、通知メールを送るためだけの notify.keyuki.net というサブドメインを登録しました。
普段のメール受信:ルートドメイン → Google Workspace
フォーム通知の送信:送信用サブドメイン → Resend
こうして役割を分ければ、Google WorkspaceのMXレコードを触らずに済みます。
Resendが表示したSPFとDKIMのDNSレコードは、Cloudflareとの連携画面から追加しました。どちらも「このサブドメインのメールをResendが送ってよい」と確認するための設定です。
DNSの確認が終わると、Resend側のドメイン表示がVerifiedになりました。
APIキーと通知先をCloudflare Pagesへ入れた
次に、ResendのAPIキーを作りました。
権限はFull accessではなく、メール送信だけができるSending accessにしました。さらに、今回使う送信用サブドメインに限定しています。
APIキーは作成直後に一度しか表示されません。
私は一度、コピーする前に画面を閉じてしまいました。当然あとから表示できず、そのキーを削除して作り直しています。
作り直したキーは会話やメモへ貼らず、ResendのCopyボタンからCloudflare PagesのSecret欄へ直接入れました。
Cloudflare Pagesに登録した設定は三つです。
| 設定名 | 何を入れるか |
|---|---|
RESEND_API_KEY |
Resendで作ったAPIキー |
CONTACT_EMAIL_FROM |
通知メールの送信元 |
CONTACT_EMAIL_TO |
自分が通知を受け取るアドレス |
設定を保存しただけでは、すでに公開されているサイトには反映されません。Cloudflare Pagesで再デプロイしてからテストしました。
問い合わせは先に保存して、それから通知する
Pages Functionの処理は、先に問い合わせをKVへ保存し、そのあとResendへ通知を頼む順番にしました。
もしResendが一時的に止まっていても、問い合わせ本文まで消えないようにするためです。
KVには通知結果も残します。
| 状態 | 意味 |
|---|---|
not_configured |
通知の設定がまだない |
sent |
Resendが送信を受け付けた |
failed |
Resendへの送信に失敗した |
通知に失敗しても、すでにKVへ保存できていればフォーム利用者には完了ページを表示します。管理者側ではKVの状態を見れば、通知だけ失敗した問い合わせを判別できます。
また、同じ通知が重複しないよう、問い合わせごとの識別子をResendの Idempotency-Key に使っています。
通知メールには、問い合わせに書かれた名前、メールアドレス、本文が入ります。Resendを経由する情報が増えたので、ブログのプライバシーポリシーにもそのことを追記しました。
本当に届くところまで試した
再デプロイが終わったあと、本番の問い合わせフォームから個人情報を含まないテストを送りました。
確認したのは次の四つです。
- フォームが完了ページへ移動した
- KVに問い合わせが保存された
- Resendの画面が
Deliveredになった - Gmailの受信箱へ実際に通知が届いた
最後にKVをもう一度確認すると、notificationStatus も sent になっていました。
APIが成功しただけでも、ResendがDeliveredになっただけでもなく、普段見る受信箱にメールがあるところまで確認しました。
これでようやく、問い合わせが来たことに気づけます。
通知先アドレスは今のままにした
通知先には、ブログ専用の役割アドレスを作りました。
設定したあとで、普通の contact のような役割アドレスを一つ作り、+blog を付けて振り分ければ、Google Workspaceのエイリアス枠を節約できたかもしれないと思いました。
ただ、今のアドレスですでに受信テストまで終わっています。エイリアスの空きもまだあるので、今回はこのままにします。
将来変更する場合も、コードやResendの設定を作り直す必要はありません。Cloudflare Pagesの CONTACT_EMAIL_TO を変えて再デプロイすれば切り替えられます。
ただし、プラスアドレスが外部から本当に届くかは、変更前に必ずテストします。特に土台のアドレス自体がGoogle Workspaceの追加エイリアスの場合、届くだろうと決めつけない方がよさそうです。
まだ残っている弱点
今回、通常の問い合わせならメールで気づけるようになりました。
ただしResendへの送信自体が失敗した場合、当然メールは届きません。問い合わせ本文と failed という状態はKVに残りますが、今のところ、その failed を別経路で知らせる仕組みまではありません。
問い合わせが少ないうちは、ときどきKVとResendのログを確認する運用でもよいと思っています。件数が増えたら、failed の監視や別の通知先を追加した方がよさそうです。
完全に「絶対見落とさない」仕組みになったわけではありません。
それでも、以前のように正常な問い合わせまで黙ってKVへ積まれる状態からは、かなり改善しました。
問い合わせは多分そんなに来ません。でも、少ないからこそ毎日管理画面を見に行く運用は続かないと思います。
送信できることだけで安心せず、自分の受信箱に届くところまで試しておいてよかったです。