フォームをメールに送る

フォームをメールに送る1 分で読めます

キーをクライアントに置かない EmailJS の代替

お問い合わせフォームを EmailJS から移行。ブラウザー側のメールキーのリスク、コードの変更点、自動返信など失うものを解説します。

Preet SutharIkiform 創業者

EmailJS はブラウザーからメールを送るので、サービス ID、テンプレート ID、公開キーがページの JavaScript に含まれ、誰でもコピーできます。フォームバックエンドならブラウザーにキーは要りません。フォームは URL に送信し、バックエンドがスパムを除外し、メッセージを保存し、あなたにメールで通知します。EmailJS の訪問者への自動返信はなくなるので、代わりに自分のメールツールから送ります。

ここが違う

EmailJS が人気なのには理由があります。サーバーなしで、静的サイトから本物のメールを送れるからです。そのためにブラウザーは、サービス ID、テンプレート ID、公開キーの 3 つの値で EmailJS を直接呼び出します。これらはバンドルに含まれ、ページのソースやネットワークタブで読めます。

EmailJS 自身の FAQ には、これらをコピーした人が送れるのはあなたのテンプレートとその内容だけで、任意のメールは送れないとあります。テンプレートの固定の文面についてはそのとおりです。しかしテンプレートの変数は呼び出し側が埋めるので、コピーしたキーを使えば、任意の名前、メッセージ、リンクを入れたテンプレートを、クォータの許す限り何度でも送れます。さらに、訪問者のアドレス宛の自動返信のようにテンプレートの To が変数なら、呼び出し側が受信者まで選べます。

ブラウザーのメールキーで送れるのは誰か、フォームのエンドポイントでは誰かブラウザーのメール SDKフォームのエンドポイント自分のページサービス、テンプレート、公開キー誰かのスクリプト3 つの値をコピー自分のテンプレート相手の文面で、To 宛に自分のページフォーム ID、秘密なしIkiformスパムスコア、レート制限、許可ドメイン自分の受信者だけダッシュボードで設定ソースを表示送信送信メールブラウザーのメールキーで送れるのは誰か、フォームのエンドポイントでは誰かブラウザーのメール SDKフォームのエンドポイント自分のページサービス、テンプレート、公開キー誰かのスクリプト3 つの値をコピー自分のテンプレート相手の文面で、To 宛に自分のページフォーム ID、秘密なしIkiformスパムスコア、レート制限、許可ドメイン自分の受信者だけダッシュボードで設定ソースを表示送信送信メール
  1. ブラウザーのメール SDK では、送信に必要なものがすべてページにあります。どこか別のスクリプトが ID とキーをコピーし、独自の文面であなたのテンプレートをクォータまで送れます。
  2. テンプレートの受信者が変数で決まるなら、コピーしたキーで受信者も選べます。
  3. フォームのエンドポイントでは、ページにあるのはフォーム ID だけです。送信はスパムを除外され、レート制限され、メールはダッシュボードで設定した受信者にしか届きません。
フォーム ID も秘密ではありません。違いは、他人がそれで何をできるかです。できるのはフォームの送信であって、あなたのメールの送信ではありません。

EmailJS には、キーを使えるドメインの制限やテンプレートへの CAPTCHA の追加など、これを絞り込む設定があります。使い続けるなら、プランに含まれているものを確認してオンにしてください。以下の比較は設計についてのもので、サービスの良し悪しの判定ではありません:

ItemEmailJSIkiform
ブラウザーが持つものサービス ID、テンプレート ID、公開キーフォーム ID
受信者を決めるものテンプレート(変数を読むこともある)ダッシュボードの設定だけ
スパムの除外追加すればテンプレートごとに CAPTCHAすべてのフォームにハニーポット、スパムスコア、レート制限。人間確認も任意で追加可能
送信後のメッセージ受信トレイの中受信トレイに加え、ダッシュボード、CSV エクスポート、API
訪問者へのメールあり。自動返信テンプレートでいいえ
独自のメールデザインあり。エディターでテンプレートを作成なし。件名 New submission: <form name> の固定レイアウト

自動返信やブランドに合わせたメールが目的なら、EmailJS か、メール API を使う自前のサーバーのほうが向いています。メッセージを安全に受信トレイへ届け、どこかに残しておきたいなら、フォームバックエンドが向いています。

フォームを移行する

EmailJS のお問い合わせフォームのチュートリアルをもとにした、React の典型的な EmailJS フォーム:

ContactForm.tsx の変更前
import emailjs from "@emailjs/browser";
import { type FormEvent, useRef, useState } from "react";

export function ContactForm() {
  const form = useRef<HTMLFormElement>(null);
  const [status, setStatus] = useState("");

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    if (!form.current) return;
    try {
      await emailjs.sendForm("service_abc123", "template_xyz789", form.current, {
        publicKey: "aBcDeFgHiJkLmNoPq",
      });
      setStatus("Message sent.");
    } catch {
      setStatus("Couldn't send your message.");
    }
  }

  return (
    <form ref={form} onSubmit={onSubmit}>
      <input name="user_name" required />
      <input name="user_email" type="email" required />
      <textarea name="message" required />
      <button>Send</button>
      <p role="status">{status}</p>
    </form>
  );
}

同じフォームを @ikiform/sdk の submit() で Ikiform に送信する版:

ContactForm.tsx の変更後
import { submit } from "@ikiform/sdk";
import { type FormEvent, useState } from "react";

const FORM_ID = "frm_xxxxxxxxxxxxxxxx";

export function ContactForm() {
  const [status, setStatus] = useState<"idle" | "sending" | "sent">("idle");
  const [fields, setFields] = useState<Record<string, string[]>>({});
  const [error, setError] = useState("");

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    setStatus("sending");
    setFields({});
    setError("");
    const result = await submit(FORM_ID, new FormData(event.currentTarget));
    if (result.ok) {
      setStatus("sent");
      return;
    }
    setFields(result.error.fields ?? {});
    setError(result.error.code === "network_error" ? "You're offline. Try again." : result.error.message);
    setStatus("idle");
  }

  if (status === "sent") return <p role="status">Message sent. We'll reply by email.</p>;

  return (
    <form onSubmit={onSubmit}>
      <label htmlFor="user_name">Name</label>
      <input id="user_name" name="user_name" required />
      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" required />
      {fields.email ? <p role="alert">{fields.email[0]}</p> : null}
      <label htmlFor="message">Message</label>
      <textarea id="message" name="message" required />
      {fields.message ? <p role="alert">{fields.message[0]}</p> : null}
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {error && !Object.keys(fields).length ? <p role="alert">{error}</p> : null}
      <button disabled={status === "sending"}>{status === "sending" ? "Sending" : "Send"}</button>
    </form>
  );
}
  • @emailjs/browser と 3 つの ID は不要になります。パッケージをアンインストールし、新しいフォームが動いたら EmailJS のテンプレートかサービスを削除して、キャッシュされた古いページから送信されないようにします。
  • user_email は email になります。EmailJS は名前を気にしませんが、Ikiform は email という名前のフィールドを返信先に使います。通知は届いた順にフィールドを並べるので、他の名前はそのままで構いません。
  • submit() は例外を投げません。エラーのエンベロープを返すので、フォームの定義によるフィールドエラーがそれぞれの入力欄の下に表示されます。
  • ハニーポットは新しく加わったものです。EmailJS にはありませんでした。この hidden の入力欄は、設定なしで単純なボットを捕まえます。
  • Allowed domains に自分のドメインを追加して、他のサイトからの送信がスパムとして扱われるようにします。
人間確認を使いますか? フォームの Spam check を Ikiform に設定し、submit の前に challenge(FORM_ID) を呼んで、トークンを turnstileToken として渡します。React ガイドに例があります。

自動返信を使っていた場合

Ikiform はフォームを入力した本人にはメールを送りません。通知メールが届くのはあなたとチームだけなので、自動返信、確認メール、ダブルオプトインはありません。それらは Webhook をきっかけに、自分のメールツールから送ってください。Resend や Postmark など、サーバーから操作する送信サービスなら、submission.created の Webhook ごとに、ブラウザーではなく信頼できるペイロードにある訪問者のアドレス宛に送れます。複数の受信者のガイドに、応用できるハンドラーがあります。

本当に必要か考えてみてください。お問い合わせフォームなら、ページに表示するお礼のメッセージで送信できたことは伝わりますし、1 時間後のあなたからの本物の返信はテンプレートに勝ります。

動作を確認する

  1. フォームの Allowed domains に自分のサイトのドメインと、開発中は localhost を追加します。空にしておくと、どこからの送信でも受け付けます。
  2. ビルドしたサイトで emailjs と古いサービス ID を検索します。何も一致しないはずです。
  3. ページのフォームに入力して送信します。自分のサンクスページか、Ikiform のサンクスページに移動します。
  4. 通知の各受信者の受信トレイを確認します。それぞれにフィールド入りのメールが届き、フォームに email フィールドがあれば Reply は送信者宛になります。
  5. ハニーポットを埋めて _gotcha=bot で送信します。成功の応答は返りますが、送信データはスパムとして保存され、メールも Webhook も送られません。

うまくいかないとき

問題対処法
返信がまだ Ikiform 宛になるフィールドがまだ user_email のままです。email、または email、email_address、your_email、reply_to、contact_email のどれかに名前を変えてください。
422 validation_failed送信がフォームのフィールド定義に合っていません。error.fields に各フィールドと理由が示されます。入力欄の name をフィールド ID に合わせてください。
403 challenge_requiredフォームのスパムチェックが Ikiform に設定されていて、fetch の送信にトークンがありませんでした。送信のたびに @ikiform/sdk の challenge(formId) でトークンを取得するか、チェックをオフにしてください。
403 origin_not_allowed が返るか、すべての送信がスパムになるページのドメインがフォームの Allowed domains にありません。追加し、ローカルでのテスト用に localhost も追加してください。登録したドメインのサブドメインは許可されます。

よくある質問

手順で詰まったときや、あなたの状況に合うガイドがほしいときはメールでどうぞ: hello@ikiform.com