静的サイトにお問い合わせフォームを無料で付ける|Cloudflare Workers×Email Routing×Turnstile
目次
このブログをWordPressからCloudflare(Astro×Workers)に移行したときに、一番「どうしよう」と思ったのがお問い合わせフォームでした。
WordPressなら Contact Form 7 などのプラグインを入れるだけですが、静的サイトにはフォームの内容を受け取ってメールを送る仕組みがありません。外部のフォームサービスを使う手もありますが、無料プランだと広告が出たり、送信数に上限があったりします。
そこで、Cloudflareの無料プランだけでお問い合わせフォームを作りました。
全体の仕組み
登場するのは次の3つで、どれも無料プランで使えます。
| 役割 | 使うもの |
|---|---|
| フォームの送信を受け取る | Cloudflare Workers |
| メールを送る | Email Routing(Worker からのメール送信) |
| スパム対策 | Turnstile |
流れはこうです。
フォームを送信する
ブラウザから
/api/contactに、入力内容と Turnstile のトークンを POST します。Worker がトークンを確認する
Worker が Turnstile の API にトークンを送り、人間による送信かどうかを確認します。
Worker がメールを送る
確認できたら、Email Routing の仕組みを使って、自分のメールアドレスにお問い合わせ内容を送ります。
このブログは静的ファイル(HTML)を Workers の静的アセットとして配信していて、静的ファイルが見つからないリクエストだけ Worker のプログラムが動く設定にしています。/api/contact には対応するファイルが無いので、ここへの送信だけが Worker に届きます。
前提:Email Routing を設定しておく
Worker からメールを送るには、そのドメインで Email Routing が有効になっている必要があります。
このブログでは、もともと info@takeiho.com 宛てのメールを Email Routing で Gmail に転送していたので、その設定をそのまま使いました。
wrangler.jsonc の設定
Worker からメールを送るための設定(send_email バインディング)を追加します。
{
"send_email": [{ "name": "MAIL", "destination_address": "転送先のアドレス@gmail.com" }],
"vars": {
"CONTACT_FROM": "info@takeiho.com",
"CONTACT_TO": "転送先のアドレス@gmail.com"
}
}destination_address を指定しておくと、このバインディングからはそのアドレスにしか送れなくなります。万が一プログラムに不具合があっても、他人にメールが送られることはありません。
フォームの HTML
フォームはふつうの HTML です。ポイントは次の2つです。
- Turnstile のウィジェット(
cf-turnstile)を置く - ボット対策に、人間には見えない入力欄(ハニーポット)を1つ置く
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<form id="contact-form" method="post" action="/api/contact">
<input name="name" required maxlength="100" />
<input name="email" type="email" required maxlength="200" />
<input name="subject" maxlength="200" />
<textarea name="message" required maxlength="5000"></textarea>
<!-- 人間には見えない欄。ここに入力があればボットとみなす -->
<input type="text" name="website" class="hp" tabindex="-1" autocomplete="off" aria-hidden="true" />
<div class="cf-turnstile" data-sitekey="サイトキー" data-language="ja"></div>
<button type="submit">送信する</button>
</form>送信は JavaScript の fetch で行い、ページを移動せずに「送信しました」と表示するようにしています。失敗したときは turnstile.reset() でウィジェットをリセットして、もう一度送れるようにしておきます。
Worker の処理
Worker では、入力チェック → Turnstile の確認 → メール送信の順に処理します。
Turnstile のトークンを確認する
フォームから送られてきたトークン(cf-turnstile-response)を、シークレットキーと一緒に Turnstile の API に送って確認します。
const token = String(form.get('cf-turnstile-response') ?? '');
const verify = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
method: 'POST',
body: new URLSearchParams({ secret: env.TURNSTILE_SECRET_KEY, response: token }),
});
const result = (await verify.json()) as { success: boolean };
if (!result.success) return json({ ok: false, error: 'スパム対策の確認に失敗しました。' }, 400);シークレットキーは設定ファイルに書かず、npx wrangler secret put TURNSTILE_SECRET_KEY で Worker に登録しています。
メールを組み立てて送る
Worker からのメール送信では、メールの中身をMIME 形式の文字列で自分で組み立てる必要があります。日本語の件名や本文が文字化けしないように、Base64 でエンコードしています。
import { EmailMessage } from 'cloudflare:email';
const raw = [
`From: =?UTF-8?B?${base64Utf8('takeiho.com お問い合わせ')}?= <${env.CONTACT_FROM}>`,
`To: <${env.CONTACT_TO}>`,
`Reply-To: <${email}>`,
`Subject: =?UTF-8?B?${base64Utf8(`【お問い合わせ】${subject}`)}?=`,
`Message-ID: <${crypto.randomUUID()}@takeiho.com>`,
`Date: ${new Date().toUTCString()}`,
'MIME-Version: 1.0',
'Content-Type: text/plain; charset=UTF-8',
'Content-Transfer-Encoding: base64',
'',
base64Utf8(body).replace(/.{76}/g, '$&\r\n'),
].join('\r\n');
await env.MAIL.send(new EmailMessage(env.CONTACT_FROM, env.CONTACT_TO, raw));Reply-To に問い合わせた人のメールアドレスを入れておくと、Gmail で「返信」を押すだけで相手に返信できます。
動作確認
npm run preview(wrangler dev)で、Cloudflare と同じ環境を手元で動かして確認しました。手元では実際のメールは届かないので、入力チェックや Turnstile の動き、エラーメッセージの表示を中心に確認しています。
本番に公開したあとは、実際にフォームから送信して、Gmail に届くこと、返信ボタンで相手のアドレスが入ることを確認して完了です。
よくある質問
無料プランで、問い合わせた人に自動返信メールを送れますか?
Email Routing の Worker からのメール送信は、認証済みの転送先アドレスにしか送れないため、問い合わせた人への自動返信はできません。自分宛てに届いたメールから返信する形になります。
Turnstile は無料ですか?
はい。Cloudflare のアカウントがあれば無料で使えます。画像を選ばせるような CAPTCHA と違い、ほとんどの場合は何も操作せずに確認が終わります。
フォームの送信で Worker の無料枠を使い切ることはありますか?
Workers の無料プランは 1 日 10 万リクエストまでです。このブログの構成では、Worker が動くのはお問い合わせの送信や旧 URL のリダイレクトなどに限られるため、個人ブログで上限に届くことはまずありません。
まとめ
- 静的サイトでも、Cloudflare Workers を使えばお問い合わせフォームを無料で作れる
- メールは Email Routing の仕組みで送る。宛先は認証済みの転送先アドレスに限られる
- スパム対策は Turnstile とハニーポットの組み合わせで十分
人気記事のランキングも、同じように Cloudflare の無料の機能で作っています。
関連記事静的サイトで人気記事ランキングを自動更新する|Cloudflare Web AnalyticsのAPI×GitHub Actions