コンテンツにスキップ

問い合わせフォーム

@takuhon/contact は軽量なチャット風の問い合わせフォームです。実体はメールで届く 1 回の ステートレスな POST で、データベース・履歴・自動返信はありません。Cloudflare 固有の 2 点 (Turnstile チャレンジと Cloudflare の send_email)はポータブルな seam 経由で注入されるため、フォームのコアはホスト非依存です。

標準的な Cloudflare デプロイ(@takuhon/cloudflarecreate-takuhon が生成するもの)では、 フォームは ターンキー です。コードではなく設定で有効化します。サーバーレンダリングされる プロフィールページがウィジェットを埋め込み、Worker がその資産と送信エンドポイントを配信します。

settings.contact公開 の Turnstile サイトキーを追加します。シークレットはドキュメントに 入れません。ここに置くのは公開値だけです:

"settings": {
"contact": {
"enabled": true,
"turnstileSiteKey": "0xYOUR_PUBLIC_SITE_KEY",
"subjectPrefix": "[example.com contact]"
}
}

subjectPrefix は任意です(配信メールの件名に前置きされます)。手書きの代わりに、管理フォームの Contact form セクションで編集することもできます。

wrangler.toml に受信先と From を設定し、シークレットを登録します。フォームが必要とする環境値は これだけです:

wrangler.toml
[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 つの受信箱に固定
Terminal window
wrangler secret put TAKUHON_TURNSTILE_SECRET # Turnstile のシークレットキー

以上です。再デプロイすれば、問い合わせウィジェットがプロフィールページに表示され、メッセージが 受信箱に届きます。create-takuhon の scaffold はこのブロックを wrangler.toml にコメントアウト した状態で同梱するので、有効化はおおむねコメントを外してアドレスを埋めるだけです。

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 “バックエンドのエンドポイント”
Terminal window
npm install @takuhon/contact

Worker の他のルートより前に 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 以外のホストでは、そのプラットフォーム向けに ChallengeVerifierEmailTransport の seam を実装します。ホスト固有なのはこの 2 つだけです。)

パッケージは自己完結した dist/contact-widget.jsdist/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-localelang から導出ロケールの明示指定
pageUrl / data-page-urllocation.hrefメールのメタデータに含める

サイトキーが設定されているときだけ注入すれば、未設定のデプロイは何もマウントせず安全です。

  • ステートレス。 メールで届く 1 回の POST。保存メッセージ・履歴・サードパーティの 受信箱を持たず、プライバシー上の露出を最小化します。
  • ポータブル。 ChallengeVerifierEmailTransport の seam によってコアは Cloudflare 固有部分を持たず、別ホストではこの 2 つの seam を実装するだけで動きます。