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 つに絞られます。
| Resend | SendGrid | Amazon SES | |
|---|---|---|---|
| 提供開始 | 2023 年 | 2009 年(Twilio 傘下) | 2011 年 |
| 無料枠 | 3,000 通/月・100 通/日・1 ドメイン | 100 通/日(永久) | 62,000 通/月(EC2 経由のみ) |
| ドメイン認証 | DNS 3 行(SPF / DKIM / 戻り MX) | DNS 数行 + 検証ダッシュボード | DKIM + IAM ロール |
| SDK | resend(軽量・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 つ:
| 種別 | 名前 | 用途 |
|---|---|---|
| TXT | resend._domainkey.<domain> | DKIM(公開鍵で送信元の真正性を検証) |
| TXT | send.<domain>(または直下) | SPF(このドメインの送信を Resend に許可) |
| MX | send.<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 dev で env.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 ステップでメールは届くようになる
最小手順を振り返ります。
- Resend ダッシュでドメイン認証(DNS 3 行 + 任意で DMARC 1 行)
- API キーを発行 →
wrangler secret put RESEND_API_KEY src/index.tsでfetch('https://api.resend.com/emails')を 1 つ書くwrangler devで自分宛に届くか確認 →wrangler deploy
この 4 ステップを通せば、もう「メールが詰まる」が個人開発の進行を止めることはなくなります。確認メール・通知・領収書 ── 1 対 1 のトランザクションメールは、Worker のなかに 1 つの関数として住まわせる形でほぼ完結します。
本番運用に入ったら、対の話として、送ったメールが届いたあとに何が起きているかも見ておきたくなります。サイト側のサインアップ計測は 個人開発者が GA4 で最初に設定する 3 つのイベント で扱った sign_up イベントが入り口です。確認メールのリンクを踏んでサイトに戻ってきたユーザーが、その先で何を見ているか ── ここまで繋がって初めて「メール 1 通の効き目」が読み取れます。
そして、確認メールが届くようになった次に詰まるのは、ローンチメールです。届いているのに登録が動かない、開かれているのに Pro に切り替わらない ── その構造をひと記事で解いたのが 個人開発者のローンチメールが素通りされる 3 つの理由 です。HOW(届かせる)→ WHY(動かす)の順で読むと、メール戦場の全体が立体的に入ります。
メールがサイレントに死ぬ時間は、これで終わりにできます。
トランザクションメールは届くようになった。けれど、開かれてもサインアップに繋がらない ── そのとき直すのは、配信ツールでも文章の熱量でもなく、順序です。WHY → HOW を 1 冊にまとめたのが、下記の本です。個人開発者・インディー Web 作者・動画作者向けに、LP・短文・メールの戦場別の書き方と、Claude を実際に使ったプロンプト連鎖の実演を含めて統合しています。