Resend で個人開発の最小トランザクションメールを送る手順

個人開発のサービスに、ユーザーが「サインアップ」したとします。次に起きるのは、メールアドレスの所有確認です。確認リンクが届かないと、その先のすべてが進まない ── ログインも、パスワードリセットも、領収書も、すべて 1 通目の確認メールに乗っています。サインアップは、送信ボタンを押した瞬間ではなく、確認メールが届いた瞬間に完成します

ところが、個人開発者が最初にぶつかるのが、この「確認メール 1 通」のセットアップです。SendGrid は登録だけで一仕事、SES は IAM とドメイン設定の泥沼、自前 SMTP は迷惑メール判定で落ちる ── 結局「メールはあとで」とコメントアウトされたまま、何ヶ月も止まる、という景色をよく見ます。

Resend は、その止まりを解消するために 2023 年に立ち上がった、個人開発者と相性のよいトランザクションメール API です。無料枠だけで月 3,000 通、ドメイン認証は DNS に 3 行、SDK は要らず fetch 1 つで動きます。本記事では、これを使って 「Cloudflare Workers から確認メールを 1 通送るまで」 を、一次情報でまとめます。

姉妹記事として、配信したメールが届いても登録に至らない構造を解いた 個人開発者のローンチメールが素通りされる 3 つの理由 を書いています。確認メール(HOW)は届かせるための話、ローンチメール(WHY)は届いた後に動かすための話 ── 2 本で「メール戦場」のうえと下を押さえます。

なぜ Resend なのか(SendGrid / SES との違い)

個人開発者のトランザクションメールの選択肢は、現実的には 3 つに絞られます。

ResendSendGridAmazon SES
提供開始2023 年2009 年(Twilio 傘下)2011 年
無料枠3,000 通/月・100 通/日・1 ドメイン100 通/日(永久)62,000 通/月(EC2 経由のみ)
ドメイン認証DNS 3 行(SPF / DKIM / 戻り MX)DNS 数行 + 検証ダッシュボードDKIM + IAM ロール
SDKresend(軽量・fetch ベース)@sendgrid/mail(Node 依存あり)@aws-sdk/client-ses(IAM 必要)
Workers / Edge 適合◎(fetch のみで動く)△(Node 依存ライブラリあり)△(IAM 署名要)
個人の温度感個人開発者向けに UX 整備エンタープライズ寄りAWS 文化を要求

「Workers から fetch だけで投げたい」「DNS は 5 分で済ませたい」「無料枠だけで個人の運用が成立する」── この 3 つを満たすのが、いま個人開発者にとっての Resend です。

なお Resend は トランザクションメール向け(確認・通知・領収書など 1 対 1 のメール)です。マーケティング・購読リストへのバルク配信は別領域で、Kit / Beehiiv / Substack 等を別途使うほうが向きます(ローンチメール WHY 記事 末尾と同じ立てつけです)。

手順 1:アカウント作成 + ドメイン認証(DNS)

resend.com で GitHub ログイン → 1 分でダッシュボードに入ります。

入ったらやることは 1 つ:Domains → Add Domain で送信元ドメイン(例:mail.example.com または example.com)を登録します。すると、登録すべき DNS レコードが画面に表示されます。必須は 3 つ:

種別名前用途
TXTresend._domainkey.<domain>DKIM(公開鍵で送信元の真正性を検証)
TXTsend.<domain>(または直下)SPF(このドメインの送信を Resend に許可)
MXsend.<domain>バウンス・苦情のフィードバック受信

任意で DMARC_dmarc.<domain> の TXT、v=DMARC1; p=none;)を 1 行足すと、受信側の信頼度がさらに上がります。p=none は警告だけで配信を止めないので、最初の設定値として安全です。

Cloudflare で DNS を持っているなら、ダッシュ → DNS → Records から 3 行コピペで終わりますProxy(オレンジ雲)は OFF(DNS Only)にする必要がある点だけ注意します(Proxy 越しだと TXT / MX が見えなくなります)。

設定後、Resend ダッシュの「Verify Domain」を押すと、数十秒〜数分で全部の行に緑のチェックが付きます。ここを通せば送れる、通さなければ送れない。シンプルです。

無料プランは 1 ドメインまで。サブドメイン(mail.example.com)で取れば本体(example.com)の Web トラフィックには影響しないので、最初は サブドメイン認証を推奨します。

手順 2:API キーを Workers のシークレットへ

ダッシュ → API Keys → Create で新しいキーを作ります。「Sending access」だけのキー(読み取り権限なし)で十分です ── 漏れたときの被害を限定する原則です。

キーは画面に 1 度だけ表示されます。すぐに Workers のシークレットへ登録します(手元のターミナル履歴に残さない原則は Cloudflare Workers の API キー運用 で書いたのと同じです)。

cd my-api  # wrangler init 済みの Worker プロジェクト
npx wrangler secret put RESEND_API_KEY
# プロンプトで貼り付け → Enter

ローカル開発の .dev.vars にも同じキーを書いておくと、wrangler devenv.RESEND_API_KEY が読めます(.gitignore 必須)。

# .dev.vars(Git に入れない)
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxx

手順 3:src/index.ts で送信関数を 1 つ書く

Resend は fetch ベースの REST API なので、SDK を入れなくても動きます。最小構成はこうです。

interface Env {
  RESEND_API_KEY: string;
}

async function sendVerificationEmail(env: Env, to: string, link: string) {
  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: 'NovelArena <noreply@mail.example.com>',
      to,
      subject: 'メールアドレスの確認',
      html: `<p>下のリンクから確認を完了してください。</p>
             <p><a href="${link}">${link}</a></p>
             <p>このメールに心当たりがない場合は破棄してください。</p>`,
    }),
  });

  if (!res.ok) {
    const err = await res.text();
    throw new Error(`Resend failed: ${res.status} ${err}`);
  }
  return res.json();
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname === '/api/verify' && request.method === 'POST') {
      const { email } = await request.json<{ email: string }>();
      const link = `https://example.com/verify?token=...`;  // 実装側で発行
      await sendVerificationEmail(env, email, link);
      return Response.json({ ok: true });
    }

    return new Response('Not Found', { status: 404 });
  },
};

from の左半分(NovelArena)は表示名、右半分(noreply@mail.example.com)は 手順 1 で認証したドメイン配下のアドレスである必要があります。ここを認証外のアドレス(@gmail.com 等)にすると、API は 403 を返します。

SDK を使いたい場合は npm install resend で同じことを書けます。Workers でも動きますが、fetch のほうがバンドル軽量・依存ゼロで挫折点が少ないため、本記事は直 fetch を推奨します。

手順 4:ローカル確認 → デプロイ

npx wrangler dev
curl -X POST http://localhost:8787/api/verify \
  -H 'Content-Type: application/json' \
  -d '{"email":"your-self@example.com"}'

数秒で確認メールが届けば OK。届かない場合は、Resend ダッシュ → Logs に送信ログが残るので、Bounced / Failed のどちらで落ちているかが見えます(ダッシュの Logs は無料枠でも全件見えます)。

問題なければ本番へ。

npx wrangler deploy

https://my-api.<your-subdomain>.workers.dev/api/verify に POST すれば、エッジ越しに同じ動作が走ります。1 通目の確認メールが届いた瞬間が、サインアップの完成点です。

挫折ポイント

  • 送れるが届かない:Gmail / Outlook の迷惑メール判定。受信側で「迷惑メール」フォルダを確認 → 入っていたら、DMARC レコード(_dmarc.<domain>v=DMARC1; p=none;)を追加すると改善します。p=none は警告だけで配信を止めないので、最初の設定値として安全。
  • 無料枠 3,000 通/月・100 通/日:ダッシュ → Usage で残量が見えます。1 日 100 通の上限があるため、ローンチ告知のような同時大量送信には向きません(それはマーケティング配信ツールの領域)。確認・パスワードリセット・領収書のような 1 対 1 通信なら、個人サービスでは数年保ちます。
  • from のドメイン認証ミス:認証されていないドメインから送ろうとすると 403。手順 1 のドメインが緑になっているか、ダッシュで Verify Domain を 1 度押し直すと再検証されます。
  • テストメールが自分にだけ届かない:自分のドメインから自分の Gmail に送ると、Gmail が「自己送信」と判定してフィルタする場合があります。別アドレス(友人・別キャリア)に送って動作確認するほうが確実です。
  • HTML が崩れる:HTML メールは古い OS の Outlook / Gmail で CSS の効きが弱いです。table レイアウトに戻る前に、まずは シンプルな HTML(1 段組・インラインスタイル) で投げます。React Email を使うとプレビュー付きで組めますが、最初の 1 通には不要です。
  • DKIM の TXT が長すぎて DNS に入らない:DNS の TXT は 1 行 255 文字の制限があり、DKIM の公開鍵がそれを超えると分割が必要です。CF の DNS UI は自動分割するので、Resend のダッシュから「Copy」ボタンでそのまま貼るのが最速。手で改行を入れない。
  • SPF を 2 つ書いてしまう:同じドメインに v=spf1 ... の TXT を 2 行入れると、SPF 全体が無効化されます。Resend と他の送信元(既存の Mailgun 等)を併用する場合、1 行に include を並べて統合します。

まとめ:4 ステップでメールは届くようになる

最小手順を振り返ります。

  1. Resend ダッシュでドメイン認証(DNS 3 行 + 任意で DMARC 1 行)
  2. API キーを発行 → wrangler secret put RESEND_API_KEY
  3. src/index.tsfetch('https://api.resend.com/emails') を 1 つ書く
  4. wrangler dev で自分宛に届くか確認 → wrangler deploy

この 4 ステップを通せば、もう「メールが詰まる」が個人開発の進行を止めることはなくなります。確認メール・通知・領収書 ── 1 対 1 のトランザクションメールは、Worker のなかに 1 つの関数として住まわせる形でほぼ完結します。

本番運用に入ったら、対の話として、送ったメールが届いたあとに何が起きているかも見ておきたくなります。サイト側のサインアップ計測は 個人開発者が GA4 で最初に設定する 3 つのイベント で扱った sign_up イベントが入り口です。確認メールのリンクを踏んでサイトに戻ってきたユーザーが、その先で何を見ているか ── ここまで繋がって初めて「メール 1 通の効き目」が読み取れます。

そして、確認メールが届くようになった次に詰まるのは、ローンチメールです。届いているのに登録が動かない、開かれているのに Pro に切り替わらない ── その構造をひと記事で解いたのが 個人開発者のローンチメールが素通りされる 3 つの理由 です。HOW(届かせる)→ WHY(動かす)の順で読むと、メール戦場の全体が立体的に入ります。

メールがサイレントに死ぬ時間は、これで終わりにできます。


トランザクションメールは届くようになった。けれど、開かれてもサインアップに繋がらない ── そのとき直すのは、配信ツールでも文章の熱量でもなく、順序です。WHY → HOW を 1 冊にまとめたのが、下記の本です。個人開発者・インディー Web 作者・動画作者向けに、LP・短文・メールの戦場別の書き方と、Claude を実際に使ったプロンプト連鎖の実演を含めて統合しています。

#個人開発#メールマーケティング#Cloudflare#リファレンス