EmDashで作ったサイトに、お問い合わせフォームを設置する方法を解説します。フォームに入力された内容は、メール送信サービスの「Resend(リセンド)」を使って、あなたのメールアドレスに届くようにします。あわせて、いたずら送信を防ぐためにCloudflareの「Turnstile(ターンスタイル)」も設定します。

この記事は、【公開編】Cloudflareで構築して独自ドメインで公開するまでの続きです。まだサイトを公開していない方は、先に公開編の手順を済ませてください。独自ドメインがCloudflareで管理されていることが前提になります。

お問い合わせフォームからメールが届くまでの仕組み

フォームの仕組み

お問い合わせフォームは、次の順番で動きます。

  1. 訪問者がフォームに入力して「送信」を押す
  2. Turnstileが、送信したのが人間かロボットかを確認する
  3. サイトのプログラム(Cloudflare Workers)が内容を受け取る
  4. ResendのAPIを使って、あなたのメールアドレスにメールを送る

Cloudflare Workersは、普通のサーバーのように直接メールを送ることができません。そこで、メール送信を専門に引き受けてくれるResendを使います。

用意するもの

  • 公開編で公開したEmDashのサイト
  • Cloudflareで管理している独自ドメイン
  • 受信用のメールアドレス(フォームの内容を受け取るアドレス)

Step 1 Resendのアカウントを作る

Resendは、プログラムからメールを送るためのサービスです。無料プランでも、1か月3,000通(1日100通)まで送信できます。お問い合わせフォームなら十分な量です。

  1. Resendの公式サイトを開き、「Get Started」からアカウントを作ります
  2. メールアドレスで登録した場合は、届いた確認メールのリンクを開きます
注意:Macでは、ResendのサイトをChromeなどで開くと、ボタンが表示されないことがあります。私たちが設定したときも、登録やドメイン追加のボタンが出てこず、先に進めませんでした。そのときはSafariで開き直すと、ボタンが表示されて操作できました。画面が途中で止まったように見えたら、まずSafariで試してみてください。

Step 2 Resendに独自ドメインを登録する

「info@example.com」のように、独自ドメインのアドレスからメールを送れるようにします。この設定をしないと、迷惑メールと判定されやすくなります。

  1. Resendの管理画面で「Domains」を開き、「Add Domain」を押します
  2. 独自ドメイン(例:example.com)を入力して追加します
  3. DNSに追加する設定(レコード)がいくつか表示されます。メールの送信元を証明するためのSPFやDKIMといった設定です

表示された設定を、Cloudflareに追加します。

  1. Cloudflareのダッシュボードで独自ドメインを選び、「DNS」の「レコード」を開きます
  2. Resendに表示された種類(TXT、MXなど)、名前、値を、1つずつそのまま追加します
  3. 追加が終わったら、Resendの画面で「Verify」を押します

しばらくしてステータスが「Verified」になれば完了です。反映に時間がかかることもあるので、すぐに変わらなくても少し待ってから確認してください。

Step 3 ResendのAPIキーを発行して登録する

APIキーは、あなたのサイトがResendを使うための「合い言葉」です。

  1. Resendの管理画面で「API Keys」を開き、「Create API Key」を押します
  2. 名前(例:my-site)を付け、権限は「Sending access」を選んで作成します
  3. 表示されたキー(re_で始まる文字列)をコピーします。キーはこの画面でしか表示されません

コピーしたキーを、Cloudflareに「シークレット」として登録します。プロジェクトのフォルダで次のコマンドを実行し、聞かれたらキーを貼り付けてEnterキーを押します。

npx wrangler secret put RESEND_API_KEY
注意:APIキーは、プログラムのファイルに直接書いたり、人に見せたりしないでください。キーを知られると、あなたのドメインから勝手にメールを送られてしまいます。万が一漏れてしまったら、Resendの管理画面でそのキーを削除し、新しいキーを発行し直してください。

Step 4 Turnstileでいたずら送信を防ぐ

フォームをそのまま公開すると、ロボットによる大量のいたずら送信が届くことがあります。CloudflareのTurnstileを使うと、画像を選ばせるような面倒な確認なしで、人間かどうかを自動で判定してくれます。

  1. Cloudflareのダッシュボードで「Turnstile」を開き、「ウィジェットを追加」を押します
  2. 名前を付け、ホスト名に独自ドメイン(例:example.com)を追加します
  3. 作成すると「サイトキー」と「シークレットキー」が表示されます

サイトキーは画面に埋め込む公開用のキーなので、wrangler.jsonc の vars に書いてかまいません。シークレットキーは、APIキーと同じようにシークレットとして登録します。

npx wrangler secret put TURNSTILE_SECRET_KEY

Step 5 フォームのプログラムを追加する

ここからはプロジェクトにファイルを2つ追加します。まず、wrangler.jsonc の vars に、送信元・送信先のアドレスとTurnstileのサイトキーを書きます。

"vars": {
  "EMDASH_SITE_URL": "https://example.com",
  "CONTACT_FROM_EMAIL": "お問い合わせ <info@example.com>",
  "CONTACT_ADMIN_EMAIL": "you@example.com",
  "TURNSTILE_SITE_KEY": "ここにサイトキー"
}

CONTACT_FROM_EMAIL は、Step 2でResendに登録したドメインのアドレスにします。CONTACT_ADMIN_EMAIL は、フォームの内容を受け取りたいアドレスです。

フォームを受け取ってメールを送るプログラム

src/pages/api/contact.ts というファイルを作り、次の内容を貼り付けます。Turnstileで確認したあと、Resendでメールを送る処理です。

import type { APIRoute } from "astro";
import { env as workerEnv } from "cloudflare:workers";

const env = workerEnv as unknown as Record<string, string>;

const json = (body: unknown, status = 200) =>
  new Response(JSON.stringify(body), {
    status,
    headers: { "Content-Type": "application/json; charset=UTF-8" },
  });

export const POST: APIRoute = async ({ request, clientAddress }) => {
  const form = await request.formData();
  const name = String(form.get("name") ?? "").trim();
  const email = String(form.get("email") ?? "").trim();
  const message = String(form.get("message") ?? "").trim();

  if (!name || !email || !message) {
    return json({ success: false, message: "すべての項目を入力してください。" }, 400);
  }

  // 1. Turnstileで、送信したのが人間か確認する
  const verify = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
    method: "POST",
    body: new URLSearchParams({
      secret: env.TURNSTILE_SECRET_KEY,
      response: String(form.get("cf-turnstile-response") ?? ""),
      remoteip: clientAddress,
    }),
  });
  const result = (await verify.json()) as { success: boolean };
  if (!result.success) {
    return json({ success: false, message: "認証に失敗しました。もう一度お試しください。" }, 400);
  }

  // 2. Resendでメールを送る
  const res = await fetch("https://api.resend.com/emails", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${env.RESEND_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      from: env.CONTACT_FROM_EMAIL,
      to: [env.CONTACT_ADMIN_EMAIL],
      reply_to: email,
      subject: `【お問い合わせ】${name}様より`,
      text: `お名前:${name}\nメール:${email}\n\n${message}`,
    }),
  });
  if (!res.ok) {
    return json({ success: false, message: "送信に失敗しました。時間をおいてお試しください。" }, 500);
  }

  return json({ success: true });
};

reply_to に訪問者のアドレスを入れているので、届いたメールにそのまま返信すれば、相手に返事ができます。

フォームのページ

次に、src/pages/contact.astro というファイルを作ります。テンプレートによってレイアウトの名前や場所が違うことがあるので、1行目の import は、ほかのページ(src/pages/index.astro など)に合わせて書き換えてください。

---
import Base from "../layouts/Base.astro";
import { env } from "cloudflare:workers";
const siteKey = (env as unknown as Record<string, string>).TURNSTILE_SITE_KEY;
---
<Base title="お問い合わせ">
  <h1>お問い合わせ</h1>
  <form id="contact-form">
    <label>お名前 <input name="name" required /></label>
    <label>メールアドレス <input name="email" type="email" required /></label>
    <label>お問い合わせ内容 <textarea name="message" rows="6" required></textarea></label>
    <div class="cf-turnstile" data-sitekey={siteKey}></div>
    <button type="submit">送信する</button>
    <p id="contact-result"></p>
  </form>

  <script is:inline src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
  <script is:inline>
    const form = document.getElementById("contact-form");
    const result = document.getElementById("contact-result");
    form.addEventListener("submit", async (e) => {
      e.preventDefault();
      result.textContent = "送信中です…";
      const res = await fetch("/api/contact", { method: "POST", body: new FormData(form) });
      const data = await res.json();
      if (data.success) {
        form.reset();
        result.textContent = "送信しました。お問い合わせありがとうございます。";
      } else {
        result.textContent = data.message;
      }
      if (window.turnstile) window.turnstile.reset();
    });
  </script>
</Base>

見た目は最低限なので、サイトのデザインに合わせてCSSを追加してください。

このフォームの設置は、Claude CodeなどのAIにお願いして作ってもらうこともできます。AIとの接続方法は、【AI活用編】MCPサーバーでClaude Code・Codexからサイトを構築・更新するで解説しています。

Step 6 パソコンで試してから公開する

パソコン上(npm run dev)で試すときは、シークレットの代わりに、プロジェクトのフォルダに .dev.vars というファイルを作って値を書きます。

RESEND_API_KEY=re_ここにAPIキー
TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AA

TURNSTILE_SECRET_KEY に書いている値は、Cloudflareが用意しているテスト用のキーで、必ず「人間」と判定されます。パソコンで試すときは、wrangler.jsonc の TURNSTILE_SITE_KEY も、テスト用の 1x00000000000000000000AA に一時的に変えておくと確実です。本番に戻す前に、元のサイトキーに戻すのを忘れないでください。

注意:.dev.vars にはAPIキーが入っているので、Gitなどで公開しないようにしてください。EmDashのテンプレートでは、最初から .gitignore に含まれているか確認しておくと安心です。

動作を確認できたら、いつものようにビルドしてデプロイします。

npm run build
npx wrangler deploy

独自ドメインの /contact を開いてフォームから送信し、CONTACT_ADMIN_EMAIL に設定したアドレスにメールが届けば完成です。

つまずきやすいポイント

Resendのサイトでボタンが表示されない

Macの場合、ChromeなどのブラウザでResendのサイトを開くと、ボタンが表示されないことがあります。Safariで開き直してみてください。

メールが届かない

次の3点を確認してください。

  • Resendの「Domains」で、ドメインのステータスが「Verified」になっているか
  • CONTACT_FROM_EMAIL のアドレスが、Resendに登録したドメインのものになっているか
  • 受信側の迷惑メールフォルダに入っていないか

Resendの管理画面の「Emails」で、送信の記録やエラーを確認することもできます。

「認証に失敗しました」と表示される

Turnstileのホスト名に独自ドメインが登録されているか、サイトキーとシークレットキーが同じウィジェットのものか確認してください。

APIキーを間違って公開してしまった

すぐにResendの管理画面でそのキーを削除し、新しいキーを発行します。発行したキーは、もう一度 npx wrangler secret put RESEND_API_KEY で登録し直してください。

まとめ

これで、EmDashのサイトにお問い合わせフォームを設置できました。

  1. Resendのアカウントを作った(Macで画面がおかしいときはSafariで開く)
  2. Resendに独自ドメインを登録し、CloudflareのDNSに設定を追加した
  3. APIキーを発行し、シークレットとして登録した
  4. Turnstileでいたずら送信の対策をした
  5. フォームのページと、メールを送るプログラムを追加した
  6. 動作を確認して、デプロイした

まだサイトを公開していない方や、独自ドメインの設定を確認したい方は、【公開編】Cloudflareで構築して独自ドメインで公開するまでもあわせてご覧ください。