Resendでメール送信を実装する手順:React Emailのテンプレートと送信ドメイン設定

Resendでメール送信を実装する手順:React Emailのテンプレートと送信ドメイン設定

Resendは、アプリケーションからメールを送るための開発者向けAPIです。本記事では、送信ドメインの追加とDMARCの設定、curlとNext.jsからの送信、React Emailでのテンプレート実装、毎秒10リクエストの上限や重複送信への備えまでを、2026年11月時点の公式ドキュメントにもとづいてコード付きで整理します。料金プランの通数の目安と、SendGridやAmazon SESではなくResendを選ぶ条件、逆に選ばない場面も最後に言い切ります。

まとめ:Resendのメール送信実装で先に知っておきたい結論と見送り条件

Node.jsとReactで開発しているチームが、会員登録やパスワード再設定などのトランザクションメールを送るなら、Resendは導入の手間がもっとも小さい部類に入ります。APIキーを発行し、検証済みのサブドメインを1つ用意すれば、resend.emails.send()の1回の呼び出しで送信できます。

本番で詰まるのは送信そのものより周辺です。DNSの反映待ち(通常15分程度、最大72時間)、チーム全体で毎秒10リクエストという上限、再試行による二重送信、bounceと苦情の取り込みの4点は、最初のリリース前に設計しておきます。

一方、添付ファイル付きのメールを大量に一括送信する案件や、円建ての請求書払いが必須の案件では、Resendは第一候補になりません。その場合はSendGridやAmazon SESと比べてから決めるのが安全です。

Resendの提供範囲と料金:Freeの1日100通からProの月5万通までの規模感

Resendが提供する送信APIとSDKの範囲と選べる4つの送信リージョン

Resendの中心は、https://api.resend.comに対するHTTPS専用のREST APIです。API概要のページによると、認証はAuthorization: Bearer re_xxxxxxxxx形式のAPIキーで行い、HTTPでの接続は受け付けません。Node.jsやPythonなど主要言語のSDKがあり、Node.jsのSDKだけはReactコンポーネントをそのまま本文として渡せます。

送信元のリージョンは、ドメインを追加するときに選びます。リージョンの説明ページに載っている選択肢は、北バージニア(us-east-1)、アイルランド(eu-west-1)、サンパウロ(sa-east-1)、東京(ap-northeast-1)の4つです。日本の利用者に送るシステムなら東京を選んでおくのが自然な判断になります。

Free・Pro・Scaleの通数と超過単価から月額を見積もる早見表

公式の料金ページに掲載されているトランザクションメールのプランを、2026年11月時点の値で整理しました。価格はすべて米ドル建てです。

プラン 月額 月間の通数 超過1,000通あたり 1日の上限 ドメイン数
Free $0 3,000通 超過不可 100通 3
Pro $20/$35 5万通/10万通 $0.90 なし 10
Scale $90〜$1,150 10万〜250万通 $0.90〜$0.46 なし 1,000

Freeは1日100通で頭打ちになるため、会員登録が1日に100件を超えうるサービスでは、開発用と割り切ります。日次の枠はUTCの0時、日本時間では午前9時に戻ります。月5万通前後の規模ならProの$20で収まり、超過分も1,000通あたり$0.90と読みやすい料金体系です。マーケティング向けの一斉配信は別体系で、送信数ではなくコンタクト数(Freeは1,000件)で上限が決まります。

送信ドメインの認証設定:サブドメイン追加からSPF・DKIM・DMARCまで

東京リージョンで送信用サブドメインを追加してDNSに登録する手順

Resendはルートドメインではなく、updates.example.comのような送信専用のサブドメインから送ることをドメインの説明ページで推奨しています。パスワード再設定などの通知と、販促メールの評判を分けられるためです。手順は次のとおりです。

  1. 管理画面のDomainsで、サブドメイン名と東京(ap-northeast-1)を指定して追加する
  2. Recordsタブに表示されたSPF用のTXTとMX、DKIM用のTXTを、表示どおりの値でDNSに登録する
  3. Cloudflareを使う場合は、CNAMEレコードのプロキシ(オレンジ色の雲)を外す
  4. 反映を待ち、15分程度で検証済みにならなければ数時間おいて確認する。72時間を過ぎても進まなければRestart verificationを押す

ドメイン追加の手順ページは、レコードの値を「Resendが生成したものと完全一致させる」よう求めています。手で打ち直さず、画面からコピーして貼るのが確実です。SPFの考え方そのものはSPFレコードの書き方と設定の判断基準で詳しく扱っています。送信元の住所にあたるReturn-Pathは、既定でsendから始まるサブドメインになります。

DMARCをp=noneから始めてquarantine・rejectへ上げていく設定例

SPFとDKIMが通ったら、DMARCのTXTレコードを足します。ResendのDMARCガイドは、最初から厳しいポリシーにせずp=noneで監視から始めるよう勧めています。レポートの宛先は自社で受け取れるアドレスに置き換えてください。

; 1段階目:監視だけ行う(失敗したメールも受信側は通常どおり扱う)
_dmarc.example.com.  3600  IN  TXT  "v=DMARC1; p=none; rua=mailto:REPORT_ADDRESS;"

; 2段階目:全送信元で dmarc=pass を確認できたら隔離へ
_dmarc.example.com.  3600  IN  TXT  "v=DMARC1; p=quarantine; rua=mailto:REPORT_ADDRESS;"

; 3段階目:正規のメールがすべて合格すると確信できたら拒否へ
_dmarc.example.com.  3600  IN  TXT  "v=DMARC1; p=reject; rua=mailto:REPORT_ADDRESS;"

DMARCは、SPFとDKIMのどちらか一方に合格すれば通ります。社内の別システムが同じドメインで送っている場合、そちらが認証を通していないとp=rejectで届かなくなります。集計レポートで送信元を棚卸ししてから段階を上げるのが、事故を避ける順番です。

curlとテスト用アドレスで最初の1通を送る:APIキー発行から到達確認まで

curlでPOST /emailsを呼ぶリクエスト例とUser-Agent必須の落とし穴

APIキーを発行したら、まずcurlで送信を確かめます。リクエストの形はSend Emailのリファレンスどおりで、from・to・subjectが必須です。送信元と宛先は環境変数MAIL_FROMとMAIL_TOに入れておき、JSONはjqで組み立てると引用符の崩れを防げます。

# 事前に RESEND_API_KEY・MAIL_FROM(表示名付きの送信元)・MAIL_TO を環境変数に設定しておく
jq -n --arg from "$MAIL_FROM" --arg to "$MAIL_TO" \
  '{from: $from, to: [$to], subject: "hello world", html: "<p>it works!</p>"}' |
curl -sS -X POST 'https://api.resend.com/emails' \
  -H "Authorization: Bearer $RESEND_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'User-Agent: my-app/1.0' \
  --data-binary @-

# 成功すると送信IDだけが返る
# {"id":"49a3999c-0ce1-4ea6-ab68-afcd6dc2e794"}

見落としやすいのがUser-Agentヘッダーです。Resendはこのヘッダーの無いリクエストを403で拒否する仕様です。curlやSDKは自動で付けるので問題になりませんが、言語標準のHTTPクライアントで直接呼ぶ実装では、APIキーが正しいのに403が返るという紛らわしい失敗になります。toは1通あたり50件まで、添付はBase64化した後で40MBまでという上限も同じページに載っています。

delivered・bounced・complainedのテスト宛先で失敗系を試す方法

実在の宛先に試し送りを繰り返すと、送信ドメインの評判を自分で下げることになります。Resendはテスト用のアドレスを4つ用意しており、resend.devドメインの次のローカル部を宛先にすると、それぞれの結果を再現できます。

  • delivered:配信成功として扱われる
  • bounced:SMTP 550 5.1.1(Unknown User)で受信拒否される
  • complained:受信後に迷惑メール報告された状態になる
  • suppressed:過去のbounceを理由に送信抑制される

先頭の3つはdelivered+signupのように+以降へラベルを付けて、シナリオごとに区別できます。suppressedだけはラベル付けに対応していません。後述のWebhook受信口は、この4種を順に送って、自社DBの状態が想定どおりに変わるかで動作確認します。

React Emailでテンプレートを実装する:6系の導入とNext.jsからの送信

react-email 6系のインストールとemailsフォルダのプレビュー確認

HTMLメールは、GmailやOutlookなど受信側ごとにCSSの解釈が違うため、Webページと同じ書き方では崩れます。React Emailは、その差を吸収する部品をReactコンポーネントとして提供するライブラリです。Manual Setupのページに沿うと、導入は次のようになります。

npm install react-email react react-dom -E
npm install @react-email/ui -D -E

# package.json の scripts に追加する
#   "email:dev": "email dev"
# emails/welcome.tsx を作成したら起動し、localhost:3000 でプレビューする
npm run email:dev
// emails/welcome.tsx
import { Body, Button, Head, Html } from "react-email";

type WelcomeProps = { name: string; url: string };

export default function Welcome({ name, url }: WelcomeProps) {
  return (
    <Html lang="ja">
      <Head />
      <Body>
        <p>{name} 様、ご登録ありがとうございます。</p>
        <Button href={url} style={{ background: "#000", color: "#fff", padding: "12px 20px" }}>
          ログインする
        </Button>
      </Body>
    </Html>
  );
}

// プレビュー画面で使う仮の値
Welcome.PreviewProps = { name: "山田", url: "https://example.com/login" } satisfies WelcomeProps;

2026年11月1日にnpm registryで確認した最新版は、react-emailが6.11.6、resendが6.37.0です。以前の解説記事で使われていた@react-email/componentsは、最新の1.0.12に「Package no longer supported」の表示が付いている状態です。現行の公式手順は部品をreact-emailから読み込む形なので、既存のコードはimport元の書き換えから始めます。GitHubのリリース履歴では、@react-email/render 2.2.0から対応するNode.jsの下限が20.19.0になったと告知されています。

Next.jsのRoute Handlerからreactパラメータでテンプレート送信

テンプレートができたら、Next.jsでの送信手順と同じ形でRoute Handlerから送ります。HTML文字列に変換せず、コンポーネントの戻り値をreactに渡すだけで済む点がResendの利点です。

// app/api/welcome/route.ts
import { Resend } from "resend";
import Welcome from "../../../emails/welcome";

const resend = new Resend(process.env.RESEND_API_KEY);

export async function POST(req: Request) {
  const { userId, to, name } = await req.json();

  const { data, error } = await resend.emails.send(
    {
      from: process.env.MAIL_FROM!, // 検証済みサブドメインの送信元
      to: [to],
      subject: "ご登録ありがとうございます",
      react: Welcome({ name, url: "https://example.com/login" }),
    },
    // 同じ利用者への歓迎メールは24時間以内なら何度呼んでも1通だけ
    { idempotencyKey: `welcome-user/${userId}` },
  );

  if (error) {
    return Response.json({ error }, { status: 500 });
  }
  return Response.json(data);
}

reactパラメータが使えるのはNode.jsのSDKだけです。PythonやGoから送る場合は、Node.js側でHTMLに変換したものをhtmlに渡す構成になります。プレーンテキスト版のtextは省略するとHTMLから自動生成されるため、通常は指定しなくても届きます。

本番運用で詰まる箇所:毎秒10リクエストの上限と重複送信とWebhook受信

429を避けるBatch APIの100通単位送信とretry-afterでの待機

Usage Limitsのページによると、既定の上限はチームあたり毎秒10リクエストです。APIキーを分けても枠はチーム内で共有される仕組みです。超えると429が返り、応答ヘッダーのretry-afterに待つべき秒数が入ります。残り回数はratelimit-remainingで読めます。

月末の請求通知のように数千通をまとめて送る処理では、1通ずつ呼ぶとすぐ上限に当たります。Batch APIのPOST /emails/batchなら1リクエストで最大100通を送れ、上限の計算でも1回と数えられます。ただしバッチでは添付ファイルを指定できません。請求書PDFを添付する設計なら、キューに積んで1秒あたり10件未満に抑えながら1通ずつ送る作りにします。

Idempotency-Keyで二重送信を防ぐキー設計と24時間の有効期限

タイムアウトしたリクエストを再試行すると、実は1回目が届いていて同じメールが2通になることがあります。これを防ぐのがIdempotency-Keyです。公式の説明では、キーは1〜256文字で24時間保持され、期間内に同じキーで再送しても新しいメールは送られず、同じ応答が返ります。

推奨されている形式はイベント種別/対象のIDです。先のRoute Handlerではwelcome-user/利用者IDにしています。同じキーで中身が違うリクエストを送ると409(invalid_idempotent_request)になり、処理中の同じキーが重なった場合も409(concurrent_idempotent_requests)が返ります。後者は時間をおいて再試行すれば安全です。

svixヘッダーで署名を検証するWebhook受信口とbounceの反映

送信APIが200を返しても、相手に届いたとは限りません。配信結果はWebhookのイベントで受け取ります。メール関連だけでもemail.sent・email.delivered・email.delivery_delayed・email.bounced・email.complained・email.failed・email.suppressedなどがあり、まず扱うべきはbounceと苦情の2つです。

// app/api/resend-webhook/route.ts
import { Resend } from "resend";

const resend = new Resend(process.env.RESEND_API_KEY);

// 宛先を自社DBの送信停止リストへ入れる処理(各システムの実装に置き換える)
declare function markUndeliverable(data: unknown): Promise<void>;

export async function POST(req: Request) {
  // JSON.parse してから文字列に戻すと署名が一致しなくなるため、生の本文を使う
  const payload = await req.text();
  try {
    const event = resend.webhooks.verify({
      payload,
      headers: {
        id: req.headers.get("svix-id") ?? "",
        timestamp: req.headers.get("svix-timestamp") ?? "",
        signature: req.headers.get("svix-signature") ?? "",
      },
      webhookSecret: process.env.RESEND_WEBHOOK_SECRET!,
    });
    if (event.type === "email.bounced" || event.type === "email.complained") {
      await markUndeliverable(event.data);
    }
    return new Response(null, { status: 200 });
  } catch {
    return new Response("Invalid webhook", { status: 400 });
  }
}

署名検証の仕組みはVerify Webhooks Requestsのページのとおり、svix-id・svix-timestamp・svix-signatureの3ヘッダーと、Webhookごとのシークレットで照合します。検証に失敗すると例外になるので、400を返して処理を止めます。署名方式と再送の扱いをSendGridと比べたい場合は、SendGrid API Event Webhookの実装手順が対になる記事です。

ResendとSendGrid・Amazon SES・Mailgunの選び分けと見送り判断

Resendを選ぶ条件:Node.jsとReactでメールの部品まで共有する開発体制

Resendが一番効くのは、フロントエンドとAPIをNext.jsなどTypeScriptでそろえている開発チームです。メールのテンプレートを画面と同じReactで書け、プレビューもローカルで回せるので、デザイン修正のたびにHTMLを手で直す工程が消えます。

送信量が月5万通前後までで、請求をクレジットカードの米ドル払いで済ませられるなら、Resendを第一候補にして構いません。月数百万通規模でも、Scaleの上位プラン(月250万通で$1,150)は超過単価も1,000通あたり$0.46まで下がるため、量だけを理由に外す必要はありません。

Resendを採用しない場面:添付付きの大量送信と円建て請求が必須の案件

採用を見送るのは、次のどちらかに当てはまる案件です。1つ目は、請求書や明細のPDFを数万件規模で毎月添付して送る処理です。Batch APIが添付に対応していないため、毎秒10リクエストの枠で1通ずつ送ることになり、1万通に最低でも17分ほどかかります。上限の引き上げをサポートに相談できる前提が無いなら、送信速度の枠が広い基盤を選びます。

2つ目は、経理の都合で円建ての請求書払いが必須の組織です。Resendの料金ページで掲載されている金額は米ドル建てです。国内代理店経由で円建て契約ができるSendGridの条件はSendGrid料金の実額と落とし穴で比較しています。外部サービスを一切使わず自社でSMTP基盤を持つ選択肢は、メール配信システムの自作・構築で到達率の確保まで含めて整理しています。

送信基盤の設計を外部に相談したほうが早い分岐点:送信元3系統以上と移行

送信APIを呼ぶ部分は半日で書けます。手間がかかるのは、既存の会員DBや基幹システムとつなぎ、bounceや苦情を送信停止リストへ戻し、DMARCをp=rejectまで上げても社内の他システムのメールが落ちないことを確かめる工程です。送信元のシステムが3つ以上ある、あるいは既存のSMTPリレーからの移行を伴う場合は、設計の段階で棚卸しを入れたほうが手戻りが減ります。一創ではAPI開発・システム連携として、メール送信APIと業務システムの連携設計から実装まで受託しています。

よくある質問

導入前に確認されることの多い5つの質問に、公式ドキュメントの記載をもとに答えます。

Resendは無料でどこまで本番運用できますか?

Freeプランは月3,000通、1日100通、ドメイン3つまでです。日次の枠はUTCの0時に戻るので、日本時間では毎朝9時に回復します。問い合わせフォームの自動返信のように1日数十通で収まる用途なら本番でも足ります。会員登録の確認メールなど、利用者数に比例して増える通知を載せるなら、上限到達で送信が止まる前にProへ切り替える前提で設計してください。

PythonやGoのバックエンドからでもResendで送れますか?

送れます。REST APIなので言語を問わず、Node.js以外のSDKも提供されています。ただし、Reactコンポーネントをreactパラメータでそのまま渡せるのはNode.jsのSDKだけです。PythonやGoから送る場合は、HTMLの文字列をhtmlに入れて送ります。React Emailでテンプレートを管理したいときは、ビルド時にHTMLへ書き出しておき、バックエンドはそれを読み込む構成が扱いやすくなります。

メルマガのような一斉配信にもResendを使えますか?

使えます。マーケティングメールは別の料金体系で、送信数ではなくコンタクト数で上限が決まり、Freeは1,000件、有料は5,000件で月$40からです。一斉配信は登録済みのコンタクトにだけ送れます。トランザクションメールと同じサブドメインから送ると、販促メールの苦情が通知メールの到達率に響くため、送信元のサブドメインを分けておきます。

送信リージョンを後から東京に変えられますか?

ドメインの設定画面でリージョンを切り替える項目はありません。公式の手順は、Domainsで既存のドメインを削除し、東京を選んで同じドメインを追加し直し、表示された新しい値でDNSレコードを更新する流れです。再検証が終わるまでは送れない時間帯が出る可能性があるため、本番の送信が少ない時間に作業する計画を立てておきます。

@react-email/componentsを使った既存のコードはそのまま動きますか?

2026年11月1日時点で、npm上の@react-email/components最新版(1.0.12)には「Package no longer supported」の表示が付いています。インストール済みのコードがすぐ止まるわけではありませんが、今後の修正は受けられない前提で考えるべきです。現行の公式手順はreact-emailパッケージから部品を読み込む形なので、依存を入れ替え、import文の読み込み元を書き換えてから、プレビューで表示崩れが無いかを確かめます。

関連記事

お気に入りに入れた記事の一覧

一創の公式 SNS

X では毎朝の新着記事と IT ニュースの速報を、YouTube・TikTok・Instagram・LinkedIn・Facebook では記事の解説動画を配信しています。

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.29 テックブログ ソニー生命の不正アクセス:38.5万件の対象確認と窓口、停止中の手続き
  2. 2026.10.31 テックブログ 松屋の不正アクセス:237万件の対象確認と窓口、漏れたパスワードの守り方
  3. 2026.10.31 テックブログ 無印良品の情報漏洩:MUJIアプリ最大2,726万件の対象確認となりすましメール対策
  4. 2026.10.31 テックブログ osinaの情報漏洩:約23万人の対象確認とSNS連携の解除、分析ツールへ渡すデータの絞り方
  5. 2026.05.18 テックブログ マネーフォワードで連携できない・失敗するときの対処法|エラー別の原因と再連携のやり方

RELATED POSTS 関連記事

目次