React でメールを書いて送る: React Email と送信サービスの選び方
執筆: Mara Levin ・ 2026年10月7日更新 ・ 価格とドキュメントはこの日に確認
テンプレートは React Email(MIT ライセンス、料金なし)で書き、送信は Resend、Postmark、Amazon SES のような API サービスに任せる、という二層に分けて考えると選びやすくなります。月 3,000 通までなら Resend の Free で足り、月 50,000 通なら Resend Pro が 20 ドル、SES なら 8 ドルです。ただし SES は最初に本番利用の申請が必要で、エディターもありません。
役割と費用の早見表
| ツール | 役割 | 料金 | 向く場面 |
|---|---|---|---|
| React Email | React コンポーネントで書くテンプレート | 無料(MIT) | React と TypeScript のアプリに同居させたい |
| MJML | タグで書き HTML にコンパイルするフレームワーク | 無料(MIT) | ビルド時に HTML を生成し、Git で管理したい |
| Maizzle | Vue と Tailwind CSS で書くフレームワーク | 料金ページなし | Tailwind と Vue に慣れたチーム |
| Resend | 送信 API | Free 0 ドル(月 3,000 通)、Pro 20 ドル(50,000 通)、Scale 90 ドル(100,000 通) | react パラメーターでそのまま送りたい |
| Postmark | 送信 API(トランザクション中心) | Basic 15 ドル(10,000 通)、スライダーで 50,000 通 55 ドル | 重要通知を一斉配信と分けたい |
| Amazon SES | 送信インフラ | 1,000 通あたり 0.16 ドル | AWS に詳しく、コストを最小にしたい |
| Mailtrap | 送信 API とテスト用受信箱 | Free 月 4,000 通、Basic 15 ドル(10,000 通) | 開発中のメールを実在の宛先に出さず確認したい |
出典: react.email、MJML ドキュメント、Resend、Postmark、Amazon SES、Mailtrap。2026年10月7日に確認。
React Email で書き、Resend で送る
React Email の公式ドキュメントでは、コンポーネントを react-email から読み込み、resend と react-email の二つのパッケージを入れる手順が示されています。テンプレートは通常の React コンポーネントです。
// email.tsx
import * as React from 'react';
import { Html, Button } from "react-email";
export function Email(props) {
const { url } = props;
return (
<Html lang="ja">
<Button href={url}>確認する</Button>
</Html>
);
}
export default Email; 送信側では、Resend の react パラメーターにコンポーネントを渡します。HTML への変換は Resend が行うため、自分で render を呼ぶ必要はありません。キーは環境変数に置き、From には認証済みドメインのアドレスを使います(onboarding@resend.dev は動作確認用です)。
import { Resend } from 'resend';
import { Email } from './email';
const resend = new Resend(process.env.RESEND_API_KEY);
await resend.emails.send({
from: 'Acme <onboarding@resend.dev>',
to: ['delivered@resend.dev'],
subject: 'ご登録ありがとうございます',
react: <Email url="https://example.com" />,
}); Resend 以外のサービスを使う場合は、render でコンポーネントを HTML 文字列にします。同じドキュメントには、プレーンテキスト版を作る toPlainText と、テキスト出力から要素を除く data-skip-in-text="true" の説明もあります。
import { render, toPlainText } from 'react-email';
import { Email } from './email';
const html = await render(<Email url="https://example.com" />);
const text = toPlainText(html); ローカルでの確認には、手動セットアップのガイドにある email dev のプレビューを使います。送信先を実在のアドレスにせずに崩れを見たい場合は、Mailtrap のようなテスト用受信箱も選択肢です。
Tailwind を使うときの注意
Tailwind コンポーネントは、メール本文を囲んでクラスをインライン化します。ドキュメントは、ほとんどのメールクライアントはスタイルの対応が限られると明記しています。既定の rem 単位に対応しないクライアントがあるため、pixelBasedPreset を指定して 16px 基準に切り替えます。
import { Tailwind, pixelBasedPreset, Button } from "react-email";
const Email = () => {
return (
<Tailwind
config={{
presets: [pixelBasedPreset],
theme: { extend: { colors: { brand: "#007291" } } },
}}
>
<Button
href="https://example.com"
className="bg-brand px-3 py-2 font-medium leading-4 text-white"
>
開く
</Button>
</Tailwind>
);
}; 使えないものも決まっています。prose(@tailwindcss/typography)、space-*、一つのクラス検索より複雑なセレクターに解決されるクラスは動きません。メディアクエリはインライン化できず、hover: はクライアントの対応が乏しいため、必要になる場面は多くないと書かれています。Tailwind の中に React の context provider を置くと子要素から useContext を使えないので、provider は Tailwind の外側に出します。
CSS はどこまで効くか: caniemail.com のデータ
React のコンポーネントで書いても、最後にメールクライアントが解釈する CSS は変わりません。caniemail.com のデータ(最終更新 2026年9月16日)から、設計に影響しやすい機能を抜粋します。表の値は各機能ページのデスクトップ向けの最新項目です。
| CSS 機能 | Gmail(デスクトップ Web) | Outlook(Windows) | Apple Mail(macOS) | Outlook.com | Yahoo! Mail(デスクトップ Web) |
|---|---|---|---|---|---|
| display:flex | 対応 | 非対応 | 対応 | 対応 | 対応 |
| display:grid | 対応 | 非対応 | 対応 | 対応 | 対応 |
| CSS 変数 | 非対応 | 非対応 | 対応 | 非対応 | 非対応 |
| border-radius | 対応 | 非対応 | 対応 | 対応 | 一部対応 |
| @media | 一部対応 | 非対応 | 対応 | 一部対応 | 一部対応 |
| prefers-color-scheme | 非対応 | 非対応 | 対応 | 対応 | 非対応 |
Gmail の列は desktop-webmail、Outlook(Windows)の列は caniemail の「outlook / windows」、Apple Mail の列は macOS、Yahoo! Mail の列は desktop-webmail の項目です。モバイルなど他の環境は各機能ページで確認してください。
この表から読み取れる設計上の結論は二つです。一つは、Outlook for Windows を対象にするなら flex や grid、角丸、メディアクエリに頼ったレイアウトは成立しない、という点です。もう一つは、CSS 変数やダークモード用のメディアクエリは Gmail で効かないため、「効く環境でだけ良くなる」上積みとして扱う、という点です。
MJML との違い
MJML は、<mj-section> や <mj-column> のようなタグでレスポンシブなメールを書き、エンジンが HTML を出力するフレームワークです。公式ガイドによれば、npm install mjml で入れ、CLI で mjml input.mjml -o output.html のようにコンパイルします。mjml -w でファイルを監視でき、検証レベルは skip、soft、strict の三つで、既定は soft です。
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-image width="100px" src="https://mjml.io/assets/img/logo-small.png"></mj-image>
<mj-divider border-color="#F45E43"></mj-divider>
<mj-text font-size="20px" color="#F45E43" font-family="helvetica">Hello World</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
# ビルド: mjml input.mjml -o output.html 実務上の違いは、HTML を作るタイミングです。MJML は多くの場合ビルド時に HTML を生成してリポジトリに置き、送信側で変数を差し込みます。React Email は送信時に render するので、props を型付きで渡せます。変数の置換を送信サービスのテンプレート側で行いたいか、アプリのコードで行いたいか、で選ぶと迷いません。詳しい比較はMJML と React Email の比較、Tailwind を使うチームはReact Email と Maizzle の比較を読んでください。Maizzle は公式の説明で Vue と Tailwind CSS で HTML メールを作るフレームワークとされています。
送信側の落とし穴
二重送信。Resend は、同じ Idempotency-Key のリクエストを 24 時間以内なら再送せずに同じ応答を返します。キーは 256 文字までで、welcome-user/123456789 のような形式が例として挙げられています。リトライ処理があるコードでは必ず付けます。
await resend.emails.send(
{
from: 'Acme <onboarding@resend.dev>',
to: ['delivered@resend.dev'],
subject: 'hello world',
html: '<p>it works!</p>',
},
{
idempotencyKey: 'welcome-user/123456789',
},
); SES のサンドボックス。新しいアカウントでは、検証済みのアドレスとドメイン宛にしか送れず、24 時間あたり 200 通、1 秒あたり 1 通までです。本番利用を申請するまで、ユーザー宛てのメールは出せません。
Postmark の Webhook。Postmark の Webhook は署名されず、提供元は URL への Basic 認証と許可する IP アドレスの一覧を勧めています。HMAC の検証を前提に実装すると動きません。
Stripe など外部 Webhook からの送信。送信ハンドラーは失敗時に再実行されるため、上記のキーで冪等にします。
送信を始める前に必要なもの
どのサービスも、キーを取ればすぐ本番で使える、というわけではありません。最初に当たる準備と制限を並べます。
| サービス | 事前に用意するもの | 最初に当たる制限 |
|---|---|---|
| Resend | API キーと、認証済みのドメイン(公式の Node.js ガイドの前提条件) | Free は 1 日 100 通、ドメインは 3 つまで |
| Postmark | メッセージストリームの選択。重要な通知と一斉配信を別のストリームに分けられる | Free は月 100 通で、超過はできない |
| Amazon SES | From、Source、Sender、Return-Path に使う ID の検証と、サンドボックス解除の申請 | サンドボックスでは 検証済みの宛先だけ、24 時間で 200 通、1 秒に 1 通 |
出典: Resend 料金ページ、Postmark 料金ページ、AWS の本番アクセス申請のドキュメント。2026年10月7日に確認。
月間の送信数ごとの費用
送信サービスだけの費用です。テンプレートに使う React Email と MJML は無料なので、ここには含めません。
| サービス | 月 3,000 通 | 月 10,000 通 | 月 50,000 通 |
|---|---|---|---|
| Resend | 0 ドル(Free) | 20 ドル(Pro) | 20 ドル(Pro) |
| Amazon SES | 0.48 ドル(3 x 0.16 ドル) | 1.60 ドル | 8.00 ドル |
| Postmark | 15 ドル(Basic) | 15 ドル | 55 ドル |
| Mailtrap | 0 ドル(Free は 4,000 通まで) | 15 ドル | 20 ドル |
Postmark の 50,000 通はスライダーの表示値、Resend の Free は 1 日 100 通の上限つきです。SES の金額は 1,000 通あたりの単価から計算した値で、サンドボックスの解除が前提です。
状況別の選び方
- 個人開発や社内ツール(月数千通まで): React Email と Resend の Free。1 日 100 通の上限に注意します。
- パスワードリセットや領収書など、届かないと困る通知: Postmark のメッセージストリームで一斉配信と分ける構成。
- 月数万通以上で、AWS を既に使っている: SES。ただし本番申請、バウンス処理、評判管理は自分で用意します。
- Tailwind と Vue のチーム: Maizzle も検討。React ならまず Tailwind コンポーネントの制約を確認します。
- HTML をビルド時に確定させたい: MJML と、好きな送信 API の組み合わせ。
本番前の確認項目
- HTML と一緒に、
toPlainTextで作ったテキスト版も送る。 - Outlook(Windows)では flex、grid、角丸、メディアクエリに頼れない前提で、レイアウトを見直す。
- Tailwind を使うなら
pixelBasedPresetを指定し、space-*のようなクラスを使っていないか確認する。 - 送信のリトライがあるコードには Idempotency-Key を付ける。
- 外部 Webhook を受けるハンドラーは、同じイベントが二度届いても同じ結果になるようにする。
- Postmark の Webhook は署名がないので、Basic 認証と IP の許可リストで守る。
- SES を使うなら、サンドボックス解除の申請が通っていることを確認してからユーザーに送る。
CSS の対応を設計に落とす方法はメールの CSS ガイド、ダークモードはダークモード設計、クライアントごとの検証はクライアント互換性ガイドにまとめています。
この記事に出てくる用語
| 用語 | 意味 |
|---|---|
| render | React のコンポーネントを HTML の文字列に変換する React Email のユーティリティ |
| pixelBasedPreset | Tailwind の rem 単位を 16px 基準の値に置き換える React Email の設定 |
| Idempotency-Key | 同じ送信リクエストを再試行しても二重に送られないようにするキー |
| サンドボックス | SES の新規アカウントに課される、宛先や件数の制限つきの状態 |
| メッセージストリーム | Postmark で、トランザクションメールと一斉配信を分けて管理する単位 |
| toPlainText | HTML からプレーンテキスト版を作る React Email の関数 |
よくある質問
React Email と MJML はどちらを選ぶべきですか。
テンプレートを React のコンポーネントとして書きたく、型や既存のデザイントークンを共有したいなら React Email です。XML 風のタグでレスポンシブなメールを書き、npm install mjml と CLI でビルド時に HTML を出したいなら MJML です。どちらも MIT ライセンスのオープンソースで、料金はかかりません。違いは書き方と実行のタイミングで、送信サービスの選択には影響しません。
React Email のテンプレートは Resend 以外でも使えますか。
使えます。React Email の公式ドキュメントは、どの送信サービスでも React のコンポーネントを render ユーティリティで HTML 文字列に変換して渡せると説明しており、統合ページには Resend、Nodemailer、Mailgun、SendGrid、Postmark、AWS SES などが並んでいます。Resend だけは react パラメーターにコンポーネントをそのまま渡せ、変換を Resend 側で行います。
Tailwind CSS はメールで使えますか。
React Email には Tailwind コンポーネントがあり、スタイルをインライン化します。ただし公式ドキュメントは「ほとんどのメールクライアントはスタイルに制限があり、一部のスタイルは動かない」と注意しています。Tailwind の既定は rem 単位で、対応しないクライアントがあるため pixelBasedPreset で 16px 基準に切り替えます。prose や space-* のような複雑なセレクターに解決されるクラスは使えません。
送信エラーで同じメールが二重に届くのを防ぐには。
Resend は Idempotency-Key ヘッダー(SDK では idempotencyKey オプション)に対応し、24 時間以内に同じキーで送られたリクエストは再送せず、同じ応答を返します。キーは最大 256 文字で、<イベント種別>/<エンティティ ID> の形式を勧めています。Stripe などの Webhook は失敗すると再送されるため、受け側のハンドラーも冪等にしてください。
Amazon SES は安く送れますか。
SES の料金ページでは Essentials が 1,000 通あたり 0.16 ドルで、月 50,000 通なら 50 x 0.16 ドル = 8 ドルです。ただし新しいアカウントはサンドボックスに置かれ、AWS のドキュメントによれば検証済みのアドレスとドメインにしか送れず、24 時間で 200 通まで、1 秒に 1 通までという制限があります。本番利用の申請が必要で、エディターやキャンペーン機能はありません。
ダークモード対応のメールは React で書けますか。
書けますが、クライアント側の対応が限られます。caniemail.com のデータでは @media (prefers-color-scheme) は Apple Mail(macOS、iOS)と Outlook の一部では対応、Gmail では非対応です。したがって配色は、ダークモードに切り替わらなくても読める前提で設計し、対応するクライアントにだけ上書きを足すのが現実的です。