ClearNets

Navigation

ホーム

トップ / プロダクト一覧

会社紹介

ClearNets の考え方

ブログ

開発ノート・設計思想

コツコツ。

和モダン習慣トラッカー

Merci

チップ決済 SaaS

Kotodama

AI 占い × キャラ

Paircon

家族見守り

家族おでかけガイド

週末のおでかけ先

コツコツ。を試す

← ブログ·技術·

Resend + React Email でトランザクションメールを設計する (日本・2026 年版)

ClearNets の 5 プロダクト (Merci / スキ活マーケット / clearcast / Kotsukotsu / Kotodama) すべての通知メールは Resend + React Email で送っています。累計 4 万通・エラー率 0.3% 未満・スパム率 0.05% 以下で回っており、月額は無料枠内に収まっています。本記事は「なぜ Resend か」の一般論ではなく、実装で必ずハマる 11 論点 — 選択肢比較・JSX でのテンプレ実装・ダブルオプトイン・ドメイン認証・バウンス監視・Cron バッチ・多言語化・dry-run・料金試算 — を、コードと数字付きで整理した実務ノートです。

1. トランザクションメール送信の選択肢比較 (2026 年版)

トランザクションメール (会員登録確認・パスワードリセット・購入通知など) 送信サービスは 2026 年時点で以下が主要選択肢です。ClearNets が実際に評価した際の比較。

  • Resend: 2023 年創業。API と DX が最モダン、React Email との 統合が公式。無料枠 3,000 通/月・100 通/日、Pro は $20/月で 50,000 通。Vercel との親和性が最強。日本のドキュメント少ないが、 英語ドキュメントは平易。
  • SendGrid (Twilio): 業界標準、企業採用が多い。無料枠 100 通/日。 Essentials $19.95/月で 50,000 通。管理画面が古く、テンプレートエディタは JSX を サポートしない (Handlebars 系)。IP レピュテーション管理が老舗ならではの安定感。
  • Postmark: トランザクション専業、マーケメール禁止のポリシーが 特徴で 配信到達率が業界最高クラス。無料枠なし、100 通で $15/月 スタート。Node/Python SDK は良質だが React Email 統合は自前実装。
  • AWS SES: 最安 (1,000 通あたり $0.10)。無料枠は EC2/Lambda から 62,000 通/月。ただしサンドボックス解除に承認ワークフロー必須、テンプレ管理 は自前、React 統合ゼロ。エンタープライズ大量配信向け。
  • Brevo (旧 Sendinblue): 無料 300 通/日、$25/月で 20,000 通。 マーケメール込みのオールインワン。日本語 UI あり。トランザクション単体では割高。

個人開発 / 少人数チームなら Resend か Postmark の 2 択、 月間 5 万通超えたら SES 検討、企業導入で監査要件があれば SendGrid、これが 2026 年時点での実務感覚です。

2. なぜ ClearNets は Resend を採用したか

比較の結果、ClearNets は Resend を全プロダクトで採用しています。決め手は 4 つ:

  1. Vercel との親和性: 環境変数 RESEND_API_KEY を 1 個貼るだけ、Vercel Marketplace 経由なら OAuth で自動設定。同じ会社 (Vercel/Resend) が投資関係にあり、両者のドキュメントが相互リンク。
  2. API のシンプルさ: resend.emails.send() の 1 関数 で完結、SDK も TypeScript first。SendGrid の mail.send(msg) より 40% 少ないコード量で同じことができる。
  3. React Email との公式統合: メールテンプレを JSX で書いて、 Resend に react: <MyEmail /> と渡すだけで HTML 化して送信。 「メール専用のマークアップ言語 (MJML/Handlebars) をもう覚えたくない」ニーズに直撃。
  4. 価格: 無料枠 3,000 通/月 で ClearNets 全プロダクト現在 6 割 で回っている。将来 Pro に上げても $20/月 = 約 ¥3,000。個人開発なら躊躇ゼロ。

Postmark の到達率も魅力でしたが、初期コスト $15/月 と React 統合の自前実装が 新規プロダクト立ち上げ時の摩擦になるため見送りました。事業が MRR ¥100k を 超えて到達率にシビアになる段階で再検討する予定です。

3. React Email で JSX からメールテンプレを書く

@react-email/components を使うと、メール HTML を JSX で書けます。 table レイアウトや inline スタイル (Gmail が class を消す対策) を裏で処理して くれるので、開発者は React コンポーネントを書く感覚でメールが作れる。

// emails/WelcomeEmail.tsx
import { Body, Container, Heading, Html, Link, Text } from "@react-email/components";

export function WelcomeEmail({ name, verifyUrl }: { name: string; verifyUrl: string }) {
  return (
    <Html lang="ja">
      <Body style={{ fontFamily: "sans-serif", padding: 24 }}>
        <Container>
          <Heading as="h1" style={{ fontSize: 24 }}>ClearNets へようこそ、{name} さん</Heading>
          <Text>ご登録ありがとうございます。以下のリンクからメールアドレスを認証してください。</Text>
          <Link href={verifyUrl} style={{ display: "inline-block", background: "#0891b2",
            color: "#fff", padding: "12px 24px", borderRadius: 8, textDecoration: "none" }}>
            メールアドレスを認証する
          </Link>
          <Text style={{ fontSize: 12, color: "#666", marginTop: 24 }}>
            このリンクは 24 時間有効です。心当たりがない場合は無視してください。
          </Text>
        </Container>
      </Body>
    </Html>
  );
}

// api/signup/route.ts
import { Resend } from "resend";
const resend = new Resend(process.env.RESEND_API_KEY);

await resend.emails.send({
  from: "ClearNets <noreply@clearnets.org>",
  to: user.email,
  subject: "【ClearNets】メールアドレスの認証をお願いします",
  react: <WelcomeEmail name={user.name} verifyUrl={`https://www.clearnets.org/verify?token=${token}`} />,
});

従来の <table cellpadding="0" cellspacing="0"> を手書きする 地獄と比べて、心理的コストが 1/5 になります。preview 機能も強力で、npm run email dev で localhost:3000 に一覧が立ち上がり、 パラメータを変えながらリアルタイムでレンダリング確認できます。

4. ダブルオプトイン (確認メール → click → 有効化) の実装パターン

メルマガや通知購読では ダブルオプトイン (登録直後に確認メールを送り、リンクを click したユーザーだけ有効化) を推奨します。 理由: (a) タイプミス防止、(b) スパムトラップ回避 (bot 登録の遮断)、 (c) 特定電子メール法 / GDPR / CAN-SPAM 準拠の証跡。

// 1. 購読リクエスト受付 (POST /api/subscribe)
const token = crypto.randomUUID();
await db.insert(pendingSubscriptions).values({
  email, token, expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000),
});
await resend.emails.send({
  from: "ClearNets <noreply@clearnets.org>",
  to: email,
  subject: "【ClearNets】メールマガジン購読の確認",
  react: <ConfirmEmail confirmUrl={`https://www.clearnets.org/confirm?token=${token}`} />,
});
// レスポンス: "確認メールを送信しました" (この時点ではまだ購読者名簿に入らない)

// 2. 確認リンク click 処理 (GET /confirm?token=xxx)
const pending = await db.query.pendingSubscriptions.findFirst({
  where: eq(pendingSubscriptions.token, token),
});
if (!pending || pending.expiresAt < new Date()) return notFound();
await db.transaction(async (tx) => {
  await tx.insert(subscribers).values({ email: pending.email, confirmedAt: new Date() });
  await tx.delete(pendingSubscriptions).where(eq(pendingSubscriptions.id, pending.id));
});
// リダイレクト: "購読が完了しました"

実装上の要点: (1) token は crypto.randomUUID() で 128bit エントロピー、 (2) 有効期限 24h、(3) 二重 click 対策で pending からの削除は tx 内、 (4) 「未確認のまま 7 日経過した pending」を cron で cleanup (メールが届かなかった ケースの掃除)、(5) 解除リンクを全メールに必須で入れる (特定電子メール法)。

5. 送信元ドメイン認証 (SPF / DKIM / DMARC)

独自ドメイン (@clearnets.org) からメールを送るには、SPF・DKIM・DMARC の 3 つの DNS レコード設定が必要です。 これを怠ると Gmail は 2024 年以降ほぼ確実にスパム判定します。

  • SPF: 「このドメインからのメールは、Resend の IP からのみ許可」を 宣言する TXT レコード。v=spf1 include:_spf.resend.com ~all @ に追加。
  • DKIM: 送信メールに公開鍵署名を入れて改竄検知。Resend 管理画面で「Add Domain」→ CNAME を 3 本 (resend._domainkey.clearnets.org など) DNS に追加。Cloudflare / Route53 で 5 分。
  • DMARC: SPF/DKIM が失敗した時の扱いを宣言。最初はp=none (監視のみ)、1 週間観察して問題なければ p=quarantine、 さらに 1 ヶ月後に p=reject へ段階昇格。rua=mailto:dmarc@clearnets.org でレポート受信も設定。

Resend の Domains ページで verification status が全て「Verified」に なってから初回送信を始めてください。verify 前に送ると DMARC アラインメント失敗で スパム箱直行、以降 IP レピュテーション回復に数週間かかります (実話)。

6. バウンス / スパム率の監視と対応

Gmail の 2024 年ガイドラインでは、送信元に対して以下の閾値が課されています。 超えると新規メールが軒並みスパム判定されるので必ず監視。

  • スパム苦情率 < 0.3% (理想 < 0.1%)。ユーザーが「迷惑 メール」ボタンを押した比率。
  • ハードバウンス率 < 5%。存在しないアドレスへの送信比率。
  • SPF/DKIM/DMARC pass 率 > 99%。DNS 設定が正しく維持されているか。

Resend は email.bounced / email.complained /email.delivered の webhook を送ってくれます。これを受けてbounce/complaint が発生したアドレスは自動で send suppression list に追加 する仕組みを作っておく。ClearNets では以下のテーブルで管理:

// schema.ts
export const emailSuppressions = pgTable("email_suppressions", {
  email: text("email").primaryKey(),
  reason: text("reason").notNull(),  // 'bounce_hard' | 'bounce_soft' | 'complaint'
  bouncedAt: timestamp("bounced_at").defaultNow(),
});

// 送信前チェック (全 email 送信の gate)
async function canSendEmail(to: string): Promise<boolean> {
  const suppressed = await db.query.emailSuppressions.findFirst({
    where: eq(emailSuppressions.email, to),
  });
  return !suppressed || suppressed.reason === "bounce_soft";  // soft はリトライ許可
}

7. Vercel Cron でのバッチ送信

毎日 9:00 に「昨日の売上サマリ」を送るような定期メールは、Vercel Cron + Resend の組合せで実装します。無料枠でも 1 日 2 回まで cron が使えます。

// vercel.json
{
  "crons": [
    { "path": "/api/cron/daily-summary", "schedule": "0 0 * * *" }
  ]
}

// api/cron/daily-summary/route.ts
export async function GET(req: Request) {
  // Vercel Cron からのアクセスであることを検証
  if (req.headers.get("Authorization") !== `Bearer ${process.env.CRON_SECRET}`) {
    return new Response("Unauthorized", { status: 401 });
  }
  const users = await getUsersToNotify();
  // Resend は 1 API call で最大 100 通のバッチ送信可 (batch endpoint)
  const chunks = chunk(users, 100);
  for (const c of chunks) {
    await resend.batch.send(c.map((u) => ({
      from: "ClearNets <noreply@clearnets.org>",
      to: u.email,
      subject: "本日のレポート",
      react: <DailyReport user={u} />,
    })));
    await sleep(1000);  // rate limit 対策 (10 req/sec)
  }
  return Response.json({ sent: users.length });
}

要点: (1) CRON_SECRET で外部叩き防止、(2) resend.batch.send で 1 API call = 100 通、(3) rate limit (無料枠 10 req/sec) を守る sleep、 (4) 各送信結果を DB に記録して失敗時のリトライ可能に、(5) 5,000 通超の 大量送信は Vercel Function の 10s 上限に注意し、Queue (Upstash QStash) を挟む。

8. メール文面のローカライズ (EN/JP 両対応)

海外ユーザー対応する時、メール文面も EN/JP 切替が必要です。React Email ならprops でロケール受け取り、テンプレ内で分岐 が素直。

// emails/messages.ts
export const emailMessages = {
  ja: {
    welcome: (name: string) => `${name} さん、ClearNets へようこそ`,
    confirmSubject: "【ClearNets】メールアドレスの認証",
    confirmCta: "メールアドレスを認証する",
  },
  en: {
    welcome: (name: string) => `Welcome to ClearNets, ${name}`,
    confirmSubject: "[ClearNets] Please confirm your email",
    confirmCta: "Confirm your email",
  },
} as const;

// api/signup/route.ts
const locale = user.locale ?? "ja";
const m = emailMessages[locale];
await resend.emails.send({
  from: "ClearNets <noreply@clearnets.org>",
  to: user.email,
  subject: m.confirmSubject,
  react: <WelcomeEmail name={user.name} verifyUrl={...} messages={m} />,
});

i18n ライブラリ (next-intl / react-i18next) をメール送信サーバ側で動かすと overhead が大きいので、単純な object 参照 で十分。 3 言語超えたら json 分離を検討。件名 (subject) も忘れずローカライズ、これを 怠ると英語ユーザーに「メールアドレスの認証」と件名が届いて即削除されます。

9. 開発環境での dry-run (Resend の test key 活用)

開発中に本物のメールが飛んで同僚に迷惑をかける事故を防ぐには、Resend のtest key を使います。管理画面の API Keys ページで 「Sending Access」= "Sandbox only" を選択したキーを作成、開発用.env.local にこれをセット。

  • test key で resend.emails.send() しても実際は送信されず、 管理画面の Emails タブに「Sandbox」でログのみ残る。
  • 本番との差分ゼロ のコードで開発できる (if 分岐で dry-run モードを書く必要がない)。
  • E2E テストで「メールを送信した」ことだけを assertion したい場合も test key で送って API response の id が返ってきたことを確認するだけで十分。

本番送信の実データ確認には、開発時テスト用のドメインonboarding@resend.devfrom にすれば認証ドメイン 設定なしで実際の inbox にも届きます。「本物ドメインで届く見た目確認」を したい時に便利。

10. コスト試算 — 無料枠 3,000 通/月、100k 通で $20

Resend の料金は Free / Pro / Scale の 3 段階です。実運用の見積り例:

  • Free ($0/月): 3,000 通/月・100 通/日。1 ドメインまで。 Kotsukotsu 単独ならこれで足りている (現在 MAU 300、月次通知 500 通)。
  • Pro ($20/月): 50,000 通/月・追加は 1,000 通あたり $1。 10 ドメイン。ClearNets 全プロダクト合計が 5 万通を超えたらここ。
  • Scale ($90/月): 100,000 通/月・追加 1,000 通あたり $0.85。 専用 IP・SSO・SLA。MRR ¥500k 以上のプロダクト向け。

SendGrid / Postmark と比べても Resend は「無料枠がある」+「Pro の per-1000 単価が安い」 で 個人 / 少人数チームには最適です。ClearNets 5 プロダクト合計の月次送信数は 現在約 1,800 通で、当分 Free tier で余裕を持って回せています。

11. まとめ

Resend + React Email の組合せは、2026 年時点で個人開発者〜スタートアップ規模の トランザクションメール実装として 最速で本番投入可能 な選択肢 です。API のシンプルさと React Email との統合により、従来数日かかった 「通知メール実装」が半日で完成します。ドメイン認証と bounce 処理を最初から 入れておけば、Gmail の 2024 年ガイドラインもクリアできます。

ClearNets では本記事の構成をテンプレート化しており、新規プロダクト立ち上げ時に コピペ + 数十行の追記で通知メール基盤が完成します。5 プロダクトで同じ型を使い 回せているのは、React コンポーネント化された template 群と Suppression テーブル、 Cron の型を共有ライブラリに切り出しているためです。次のプロダクトでも同じ型を 使うつもりです。


この記事を書いた背景

ClearNets は 2026 年 3 月に Kotsukotsu で Resend + React Email を採用、 その後 Merci (Stripe Connect 通知) / スキ活マーケット (購入・出金通知) / clearcast (レポート配信) / Kotodama (占い結果) に横展開しました。本記事の 数字と手順は 2026 年 8 月時点の実測に基づきます。Resend / React Email の API は活発に更新されているため、最新は resend.com/docs と react.email を参照ください。


← ブログ一覧に戻る