Cloudflare Pagesでお問い合わせフォーム(自動返信あり)を作る

公開日:2023/2/4更新日:2024/4/17

Cloudflare Pagesはホスティングサービスとして最強のスペックを誇りますが、メール送信のライブラリ(nodemailerなど)やSDKが動作しないため、お問い合わせフォームが作れないことが個人的な不満ポイントでした。が、メール配信サービスのMailChannelsとの連携でやっとお問い合わせフォームが作れるようになりました。

やり方は二通りあり、一つはプラグインを使う方法。もう一つは専用のエンドポイントを使う方法です。前者のプラグインの方が導入難易度は簡単ですが、それ故に融通が効かないので、画面遷移なしで非同期にフォーム送信したり自動返信機能を用意する場合は後者のエンドポイントを使う方法を採用してください。

プラグインを使う方法は説明不要なので、今回は後者のエンドポイントを使って構築する方法をご紹介します。

※現在、他にも必要な設定が増えました。詳細は別記事で紹介しています。

仕様

フォーム送信(メール配信)はMailChannelsを通すため、決められた仕様(ルール)があるのですが、基本的には他のメール配信サービスと同じ仕様です。

  • personalizations(送信相手の情報)
  • from(送信者の情報)
  • subject(件名)
  • content(本文)

が主要な情報で、reply_to(返信先)やccbccなどの情報を含めることもできます。

実装手順

Pages Functionsを使うために、ルートフォルダにfunctions/api/sendForm.tsとしてファイルを用意し、以下のコードを記述します。メールアドレスや文面はお好みで変更してください。

export const onRequest = async ({ request, env }) => {
  const recipientEmail = "[email protected]"; // 自分のメールアドレス
  const sender = "Shinobi Works"; // 自分の名前や会社名
  const senderEmail = "[email protected]"; // 送信に使うメールアドレス(ドメインが同じなら適当でも可)
  const formData = await request.formData(); // クライアントサイドから送られたフォームデータを取得
  const name = formData.get("name"); // フォームデータの中身
  const email = formData.get("email-address"); // フォームデータの中身
  const detail = formData.get("detail"); // フォームデータの中身

  // お問い合わせ内容をサイト運営者に送信
  const toAdminRes = await fetch("https://api.mailchannels.net/tx/v1/send", {
    method: "POST",
    headers: {
      "content-type": "application/json",
    },
    body: JSON.stringify({
      personalizations: [
        {
          to: [{ email: recipientEmail }],
        },
      ],
      from: { email: senderEmail, name: sender },
      subject: "お問い合わせがありました",
      content: [
        {
          type: "text/plain",
          value: `【お名前】\n${name}様\n【メールアドレス】\n${email}\n【お問い合わせ内容】\n${detail}`,
        },
      ],
    }),
  });

  // お客様へ自動返信メール
  if (toAdminRes.ok) {
    await fetch("https://api.mailchannels.net/tx/v1/send", {
      method: "POST",
      headers: {
        "content-type": "application/json",
      },
      body: JSON.stringify({
        personalizations: [
          {
            to: [{ email }],
          },
        ],
        from: { email: senderEmail, name: sender },
        subject: "お問い合わせありがとうございます",
        content: [
          {
            type: "text/plain",
            value: `${name}様\nこの度はお問い合わせいただきありがとうございます。(略)\n【お問い合わせ内容】\n${detail}`,
          },
        ],
      }),
    });

    return new Response("送信に成功しました!", { status: 200 });
  }

  return new Response("送信に失敗しました。", { status: 500 });
};

今回はクライアントサイドからformDataが送られてくる想定のため上記のコードになりますが、await request.text()として文字列を受け取り、JSON.parse()することもできます。

第2引数のenvには.dev.varsファイルに定義された値が格納されるため、環境変数としてメールアドレスや認証情報を読み込むことができます。

型付けの恩恵を受けるにはTypeScriptのドキュメントを参考にしてください。

クライアントサイドからフォームを送信

次にクライアントサイドからフォームを送信します。

Cloudflare Pagesではfunctionsフォルダ以下の構造がエンドポイントになり、今回はfunctions/api/sendForm.tsとしてファイルを用意しているため、エンドポイントは/api/sendFormになります。

以下はフォームを送信するサンプルコードです。

<form
  onSubmit={async (e) => {
    e.preventDefault();

    const formData = new FormData(e.currentTarget);
    const res = await fetch("/api/sendForm", {
      method: "POST",
      body: formData,
    });

    if (res.ok) {
      // 成功時の処理
    } else {
      // 失敗時の処理
    }
  }}
>
  <input type="text" name="name" required />
  <input type="email" name="email-address" required />
  <input type="text" name="detail" required />
  <button type="submit">送信</button>
</form>

注意点

メール送信には通常、配信サービスが提供するトークンや、ユーザー名やパスワードなどの情報が必要ですが、今回のメール配信プログラムでは不要です。そういった意味では最も手軽に実装可能なお問い合わせフォームです。

しかし、以下のような無視できない注意点もあります。

テストができない

地味なハマりポイントですが、今回のMailChannelsのメール送信はCloudflare環境にデプロイされたサイトだけが使えるため、開発環境でのテストができません。これは、wrangler pages dev ./distなどのコマンドを使った場合も同様です。

(開発環境でメール送信テストをしようとするとエラーが返ってきます)

ちなみに、fromに使うメールアドレスは、有効なドメインである必要があります。そのため、これから取得予定のドメインでテストしようとしてもエラーになります。(逆に、すでに稼働しているドメインであれば何でも使えます)

ファイルを添付できない

執筆時点、画像などのファイルを添付することはできません。ファイルの中身やbase64化したものをcontentの値にファイル含めたり、Nodemailerのようにattachmentsの項目を追加してみたんですが、どれも上手くいきませんでした。

以下は動作しないコードです。

fetch("https://api.mailchannels.net/tx/v1/send", {
  method: "POST",
  headers: {
    "content-type": "application/json",
  },
  body: JSON.stringify({
    personalizations: [
      {
        to: [{ email: "[email protected]" }],
      },
    ],
    from: { email: "[email protected]" },
    subject: "お問い合わせがありました",
    content: [
      {
        type: "text/plain",
        value: "問い合わせがありました。(以下略)",
      },
      {
        type: "image/jpeg",
        value: "ファイルの中身",
      },
    ],
  }),
});

この問題を回避する方法はいくつか考えられますが、今回の私のプロジェクトのバックエンドがWordPressだったため、WP REST APIを利用して画像だけ保存するプログラムを組みました。ご参考までに。

メールがスパム扱いされる可能性がある

設定不要でメール送信可能ということは、なりすましが可能ということです。例えば、abc.comのサイトの問い合わせフォームでdef.comからメール配信することが可能です。

そのため、スパム扱いされないためにDKIMの設定が推奨されています。