問い合わせフォーム
@takuhon/contact は軽量なチャット風の問い合わせフォームです。実体はメールで届く 1 回の
ステートレスな POST で、データベース・履歴・自動返信はありません。Cloudflare 固有の 2 点
(Turnstile チャレンジと Cloudflare の
send_email)はポータブルな seam 経由で注入されるため、フォームのコアはホスト非依存です。
標準的な Cloudflare デプロイ(@takuhon/cloudflare。create-takuhon が生成するもの)では、
フォームは ターンキー です。コードではなく設定で有効化します。サーバーレンダリングされる
プロフィールページがウィジェットを埋め込み、Worker がその資産と送信エンドポイントを配信します。
ターンキー設定(Cloudflare)
Section titled “ターンキー設定(Cloudflare)”1. takuhon.json で有効化する
Section titled “1. takuhon.json で有効化する”settings.contact に 公開 の Turnstile サイトキーを追加します。シークレットはドキュメントに
入れません。ここに置くのは公開値だけです:
"settings": { "contact": { "enabled": true, "turnstileSiteKey": "0xYOUR_PUBLIC_SITE_KEY", "subjectPrefix": "[example.com contact]" }}subjectPrefix は任意です(配信メールの件名に前置きされます)。手書きの代わりに、管理フォームの
Contact form セクションで編集することもできます。
2. Cloudflare の配信を設定する
Section titled “2. Cloudflare の配信を設定する”wrangler.toml に受信先と From を設定し、シークレットを登録します。フォームが必要とする環境値は
これだけです:
[vars]TAKUHON_CONTACT_TO = "you@example.com" # 受信先 — 下の注記を参照TAKUHON_CONTACT_FROM = "noreply@example.com" # 配信メールの From ラベル
[[send_email]]name = "TAKUHON_CONTACT_EMAIL"# destination_address = "you@example.com" # 任意: 1 つの受信箱に固定wrangler secret put TAKUHON_TURNSTILE_SECRET # Turnstile のシークレットキー以上です。再デプロイすれば、問い合わせウィジェットがプロフィールページに表示され、メッセージが
受信箱に届きます。create-takuhon の scaffold はこのブロックを wrangler.toml にコメントアウト
した状態で同梱するので、有効化はおおむねコメントを外してアドレスを埋めるだけです。
Turnstile のキー
Section titled “Turnstile のキー”Cloudflare ダッシュボードで Turnstile ウィジェットを
作成し、2 つのキーを取得します。サイトキー(公開。settings.contact.turnstileSiteKey に入れる)と
シークレットキー(TAKUHON_TURNSTILE_SECRET の Wrangler シークレットに入れる)です。
- ウィジェット は Worker が
/contact-widget.jsと/contact-widget.cssで配信し (バンドル同梱・資産バインディング不要)、サーバーレンダリングされるページにはdata-*属性付きの 遅延<script>で埋め込まれます。インラインスクリプトが不要なので、ページの Content-Security-Policy は厳格なまま(許可するのは Turnstile のオリジンのみ・使用するページだけ)です。 - エンドポイント
POST /api/contactは、send_emailバインディングがバインドされ かつsettings.contact.enabledが true のときだけマウントされます。メソッド /Originガード・入力 バリデーション・Turnstile 検証・配信を実行し、same-origin のみを許可します。 - 例外を投げません。 シークレットが無ければ拒否(
422)に、受信先 / From が無ければ配信失敗 (502)に劣化し、ハニーポット命中時は偽装した200を返します。クラッシュはしません。settings.contactを無効化(またはバインディングを外す)すると、ウィジェットとエンドポイントの 両方が止まります。
自前レンダラ・その他のホスト
Section titled “自前レンダラ・その他のホスト”上のターンキー手順は標準の Cloudflare アダプタ向けです。自前レンダラ(@takuhon/api の
サーバーレンダラを使わず、プロフィール HTML を自分で生成する)を使う場合や、まだターンキー
アダプタが無いホスト(Vercel / WordPress)では、2 つの半分を手動で組み込みます。
バックエンドのエンドポイント
Section titled “バックエンドのエンドポイント”npm install @takuhon/contactWorker の他のルートより前に POST /api/contact をマウントします:
import { handleContact } from '@takuhon/contact';import { createSendEmailTransport, createTurnstileVerifier } from '@takuhon/cloudflare';
// fetch(request, env) の中で:const url = new URL(request.url);if (request.method === 'POST' && url.pathname === '/api/contact') { return handleContact(request, { verifier: createTurnstileVerifier(env.TURNSTILE_SECRET ?? ''), transport: createSendEmailTransport(env.CONTACT_EMAIL, { to: env.CONTACT_TO, from: { email: env.MAIL_FROM, name: 'example.com' }, subjectPrefix: '[example.com contact]', }), config: { allowedOrigins: [url.origin] }, });}(Cloudflare 以外のホストでは、そのプラットフォーム向けに ChallengeVerifier と
EmailTransport の seam を実装します。ホスト固有なのはこの 2 つだけです。)
ウィジェット
Section titled “ウィジェット”パッケージは自己完結した dist/contact-widget.js と dist/contact-widget.css を同梱し、
バンドル用に @takuhon/contact/assets から文字列(contactWidgetJs / contactWidgetCss)
としても公開します。これらを自分のオリジンから配信し、ページにウィジェットを注入します。
CSP に優しいのはスクリプトの data-* 属性を使う方法です(インラインスクリプト不要):
<link rel="stylesheet" href="/contact-widget.css" /><script src="/contact-widget.js" data-site-key="0x..." defer></script>スクリプト読み込み前に設定する window.TAKUHON_CONTACT グローバルも読み取ります。好みで
こちらを使っても構いません:
<script>window.TAKUHON_CONTACT = { siteKey: "0x..." };</script>いずれの方法でも、サイトキーがあればウィジェットは DOM 読み込み時に自動マウントされます:
オプション(グローバル / data-*) | 既定 | 意味 |
|---|---|---|
siteKey / data-site-key | —(必須) | Turnstile の公開サイトキー |
endpoint / data-endpoint | /api/contact | フォームの POST 先 URL |
lang / data-lang | <html lang> | ウィジェットの UI 言語 |
locale / data-locale | lang から導出 | ロケールの明示指定 |
pageUrl / data-page-url | location.href | メールのメタデータに含める |
サイトキーが設定されているときだけ注入すれば、未設定のデプロイは何もマウントせず安全です。
この設計の理由
Section titled “この設計の理由”- ステートレス。 メールで届く 1 回の
POST。保存メッセージ・履歴・サードパーティの 受信箱を持たず、プライバシー上の露出を最小化します。 - ポータブル。
ChallengeVerifierとEmailTransportの seam によってコアは Cloudflare 固有部分を持たず、別ホストではこの 2 つの seam を実装するだけで動きます。