メール実装が地味に消耗する理由
決済確認やパスワードリセットのメールが、送信先のクライアントによって崩れて表示される。テーブルレイアウトを手作業で組み直しては、また別のクライアントで崩れる。トランザクションメールの実装は、地味でありながら消耗しやすい作業です。
Antigravity のエージェント機能に Resend と React Email を組み合わせると、この消耗を大きく減らせます。型安全で保守しやすいメール配信の仕組みを、テンプレート生成からAPI実装、テスト送信まで一つの流れで構築していきます。
Resend と React Email とは
Resend
Resend は、開発者ファーストなメール配信APIサービスです。従来のメール配信サービス(SendGrid, Mailgun など)と比較して、以下の特徴があります。
- シンプルなAPI: REST API と公式SDK(Node.js / Python / Go / Ruby)で即座に送信可能
- React Email との統合: JSXでメールテンプレートを書き、そのまま送信できる
- 高い配信到達率: SPF / DKIM / DMARC を自動設定
- リアルタイム分析: 開封率・クリック率・バウンス率をダッシュボードで確認
React Email
React Email は、Reactコンポーネントでメールテンプレートを構築するライブラリです。HTMLメール特有の <table> レイアウトや各メールクライアントの互換性を、コンポーネントが内部で吸収してくれます。
// React Email の基本コンポーネント例
import { Html, Head, Body, Container, Text } from "@react-email/components";
// 各コンポーネントがメールクライアント互換のHTMLを自動生成
// <Container> → 中央揃えの <table> に変換
// <Text> → Outlook対応の <p> タグに変換環境構築
前提条件
- Node.js 20 以上
- Antigravity IDE 最新版
- Resend アカウント(無料枠: 月3,000通)
- Next.js プロジェクト(App Router 推奨)
パッケージのインストール
Antigravity のターミナルを開き、以下を実行します。
# Resend SDK と React Email コンポーネントをインストール
npm install resend @react-email/components
# 開発用プレビューサーバー
npm install -D react-email
# 期待する出力:
# added 42 packages in 8s環境変数の設定
プロジェクトルートの .env.local に Resend API キーを追加します。
# .env.local
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxxxxxxxxxxTip: Antigravity のインラインチャット(
Cmd+I)で「.env.localに Resend の API キー用エントリを追加して」と指示すれば、適切な形式で自動追加してくれます。
メールテンプレートの構築
ディレクトリ構成
src/
├── emails/ # メールテンプレート
│ ├── welcome.tsx # ウェルカムメール
│ ├── payment-confirm.tsx # 決済確認メール
│ └── password-reset.tsx # パスワードリセット
├── lib/
│ └── email.ts # Resend クライアント設定
└── app/
└── api/
└── send-email/
└── route.ts # メール送信 API
ウェルカムメールテンプレート
Antigravity のエージェントに「React Email でウェルカムメールテンプレートを作成して」と指示すれば、以下のようなコンポーネントが生成されます。
// src/emails/welcome.tsx
import {
Html,
Head,
Preview,
Body,
Container,
Section,
Text,
Button,
Img,
Hr,
} from "@react-email/components";
interface WelcomeEmailProps {
userName: string;
loginUrl: string;
}
export default function WelcomeEmail({
userName = "ユーザー",
loginUrl = "https://example.com/login",
}: WelcomeEmailProps) {
return (
<Html lang="ja">
<Head />
{/* プレビューテキスト: 受信箱で件名の横に表示される */}
<Preview>{userName}さん、ご登録ありがとうございます!</Preview>
<Body style={main}>
<Container style={container}>
<Img
src="https://example.com/logo.png"
width={48}
height={48}
alt="Logo"
/>
<Text style={heading}>ようこそ、{userName}さん!</Text>
<Text style={paragraph}>
アカウントの登録が完了しました。さっそくログインして、
すべての機能をお試しください。
</Text>
<Section style={buttonContainer}>
<Button style={button} href={loginUrl}>
ログインする
</Button>
</Section>
<Hr style={hr} />
<Text style={footer}>
このメールに心当たりがない場合は、このまま無視してください。
</Text>
</Container>
</Body>
</Html>
);
}
// スタイル定義(インラインスタイルが必須)
const main = {
backgroundColor: "#f6f9fc",
fontFamily: '-apple-system, "Segoe UI", Roboto, sans-serif',
};
const container = {
margin: "0 auto",
padding: "40px 20px",
maxWidth: "560px",
};
const heading = {
fontSize: "24px",
fontWeight: "bold" as const,
color: "#1a1a1a",
marginBottom: "16px",
};
const paragraph = {
fontSize: "16px",
lineHeight: "1.6",
color: "#4a4a4a",
};
const buttonContainer = { textAlign: "center" as const, margin: "32px 0" };
const button = {
backgroundColor: "#4f46e5",
color: "#ffffff",
padding: "12px 32px",
borderRadius: "8px",
fontSize: "16px",
textDecoration: "none",
};
const hr = { borderColor: "#e5e7eb", margin: "24px 0" };
const footer = { fontSize: "12px", color: "#9ca3af" };決済確認メールテンプレート
// src/emails/payment-confirm.tsx
import {
Html,
Head,
Preview,
Body,
Container,
Section,
Row,
Column,
Text,
Hr,
} from "@react-email/components";
interface PaymentConfirmProps {
userName: string;
planName: string;
amount: string;
currency: string;
receiptUrl: string;
}
export default function PaymentConfirmEmail({
userName = "ユーザー",
planName = "Pro プラン",
amount = "¥380",
currency = "JPY",
receiptUrl = "#",
}: PaymentConfirmProps) {
return (
<Html lang="ja">
<Head />
<Preview>お支払い確認: {planName} — {amount}</Preview>
<Body style={main}>
<Container style={container}>
<Text style={heading}>お支払い完了</Text>
<Text style={paragraph}>
{userName}さん、{planName}のお支払いが正常に処理されました。
</Text>
<Section style={receiptBox}>
<Row>
<Column><Text style={label}>プラン</Text></Column>
<Column><Text style={value}>{planName}</Text></Column>
</Row>
<Hr style={hr} />
<Row>
<Column><Text style={label}>金額</Text></Column>
<Column><Text style={value}>{amount}({currency})</Text></Column>
</Row>
</Section>
<Text style={footerText}>
領収書は<a href={receiptUrl} style={link}>こちら</a>から
ダウンロードできます。
</Text>
</Container>
</Body>
</Html>
);
}
const main = {
backgroundColor: "#f6f9fc",
fontFamily: '-apple-system, "Segoe UI", Roboto, sans-serif',
};
const container = { margin: "0 auto", padding: "40px 20px", maxWidth: "560px" };
const heading = { fontSize: "24px", fontWeight: "bold" as const, color: "#1a1a1a" };
const paragraph = { fontSize: "16px", lineHeight: "1.6", color: "#4a4a4a" };
const receiptBox = {
backgroundColor: "#ffffff",
border: "1px solid #e5e7eb",
borderRadius: "8px",
padding: "24px",
margin: "24px 0",
};
const label = { fontSize: "14px", color: "#6b7280" };
const value = { fontSize: "14px", fontWeight: "bold" as const, textAlign: "right" as const };
const hr = { borderColor: "#e5e7eb" };
const footerText = { fontSize: "14px", color: "#6b7280" };
const link = { color: "#4f46e5" };Resend クライアントとAPI実装
Resend クライアント設定
// src/lib/email.ts
import { Resend } from "resend";
// シングルトンパターンで Resend クライアントを初期化
// 環境変数が未設定の場合は早期エラーで検知
if (!process.env.RESEND_API_KEY) {
throw new Error("RESEND_API_KEY is not set");
}
export const resend = new Resend(process.env.RESEND_API_KEY);メール送信APIエンドポイント
// src/app/api/send-email/route.ts
import { NextRequest, NextResponse } from "next/server";
import { resend } from "@/lib/email";
import WelcomeEmail from "@/emails/welcome";
import PaymentConfirmEmail from "@/emails/payment-confirm";
// メールタイプに応じたテンプレートのマッピング
const EMAIL_TEMPLATES = {
welcome: {
subject: "ご登録ありがとうございます!",
component: WelcomeEmail,
},
"payment-confirm": {
subject: "お支払い確認",
component: PaymentConfirmEmail,
},
} as const;
type EmailType = keyof typeof EMAIL_TEMPLATES;
export async function POST(req: NextRequest) {
try {
const { type, to, props } = await req.json();
// バリデーション
if (!type || !to || !EMAIL_TEMPLATES[type as EmailType]) {
return NextResponse.json(
{ error: "Invalid request: type and to are required" },
{ status: 400 }
);
}
const template = EMAIL_TEMPLATES[type as EmailType];
const Component = template.component;
// Resend API でメール送信
const { data, error } = await resend.emails.send({
from: "MyApp <noreply@yourdomain.com>",
to: [to],
subject: template.subject,
react: Component(props || {}),
});
if (error) {
console.error("Email send error:", error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
// 期待する出力: { id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }
return NextResponse.json({ success: true, id: data?.id });
} catch (err) {
console.error("Unexpected error:", err);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}Antigravity エージェントによる開発の効率化
Antigravity IDE の最大の強みは、エージェント機能を使ったAIアシスト開発です。メールテンプレートの構築では特に威力を発揮します。
テンプレートの自動生成
Antigravity のチャットパネルで以下のように指示するだけで、React Email のテンプレートが生成されます。
パスワードリセット用のメールテンプレートを React Email で作成してください。
- リセットリンクのボタンを中央に配置
- リンクの有効期限(1時間)を明記
- ブランドカラーは #4f46e5
- 日本語で作成
エージェントは React Email のコンポーネントAPIを理解しているため、メールクライアント互換の正しいHTMLを出力します。
プレビューとテスト
React Email には開発用のプレビューサーバーが付属しています。
# プレビューサーバーを起動
npx react-email dev --dir src/emails --port 3001
# 期待する出力:
# Ready on http://localhost:3001
# Found 3 email templatesブラウザで http://localhost:3001 を開くと、テンプレート一覧が表示され、各メールのプレビューと props の動的変更がリアルタイムで確認できます。
Resend テスト送信
開発中は Resend のテストモードを使えば、実際のメールを送らずに API の動作確認ができます。
# curl でテスト送信
curl -X POST http://localhost:3000/api/send-email \
-H "Content-Type: application/json" \
-d '{
"type": "welcome",
"to": "test@example.com",
"props": {
"userName": "テスト太郎",
"loginUrl": "https://example.com/login"
}
}'
# 期待する出力:
# {"success":true,"id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}Stripe 決済後の自動メール送信
SaaSアプリで特に需要が高いのが、Stripe 決済完了後の自動確認メール送信です。Webhook と組み合わせることで完全に自動化できます。実際の Stripe 連携の詳細については「Stripe × Antigravity 決済実装ガイド」も参考にしてください。
// src/app/api/webhook/route.ts(抜粋)
import { resend } from "@/lib/email";
import PaymentConfirmEmail from "@/emails/payment-confirm";
// Stripe Webhook: checkout.session.completed 時にメール送信
async function handleCheckoutCompleted(session: any) {
const email = session.customer_details?.email;
if (!email) return;
await resend.emails.send({
from: "MyApp <noreply@yourdomain.com>",
to: [email],
subject: "お支払い確認 — MyApp",
react: PaymentConfirmEmail({
userName: session.customer_details.name || "お客様",
planName: session.metadata?.plan_type === "pro" ? "Pro プラン" : "Premium プラン",
amount: `¥${session.amount_total / 100}`,
currency: session.currency?.toUpperCase() || "JPY",
receiptUrl: session.receipt_url || "#",
}),
});
}本番環境へのデプロイ
DNS 設定(独自ドメイン送信)
Resend で独自ドメインからメールを送信するには、DNS に以下のレコードを追加します。
# Resend ダッシュボード → Domains → Add Domain で表示される値を設定
TXT _resend re_xxxx... # ドメイン認証
CNAME send._domainkey ... # DKIM 署名
TXT _dmarc v=DMARC1; p=none; ... # DMARC ポリシー
Cloudflare Workers での注意点
Cloudflare Workers 環境で Resend を使う場合は、fetch ベースの HTTP クライアントが使用されるため、特別な設定は不要です。ただし、環境変数は wrangler secret put RESEND_API_KEY で設定してください。
サーバーレスAPIの構築パターンについては「Antigravity × Hono + Cloudflare Workers サーバーレスAPI開発ガイド」が参考になります。
まとめ
Antigravity IDE と Resend + React Email を組み合わせることで、トランザクションメールの開発・配信を大幅に効率化できます。特に Antigravity のエージェント機能は、メールテンプレートのコーディングという「地味だが手間のかかる」作業を劇的に短縮します。
今回紹介した構成のポイントをまとめると、React Email によるコンポーネントベースのテンプレート設計、Resend API を使ったシンプルな配信ロジック、Stripe Webhook との連携による完全自動化、そして Antigravity エージェントによるテンプレート生成の加速です。
Next.js でのフルスタック開発