メールをコードで管理しているチーム向けのガイドです。コーディングエージェントで棚卸しし、MCP経由で Brew のキャンバスにまとめてから、安全に移行・改善します。

コードで管理するメールとは、コードベースで定義され、ほかの機能と同じようにリリースされるプロダクトメールのことです。Resend と React Email で開発しているチームに、製品がどんなメールを送っているのかを尋ねると、一瞬の沈黙が返ってきます。ウェルカムメールは emails/ の中のコンポーネントです。パスワードリセットは、認証プロバイダーのダッシュボードにあります。トライアル終了のメールは、cron ジョブの中の文字列です。領収書は Stripe が送っています。「またお会いしたいです」というメールを何が送っているのかは、誰も覚えていません。
どのメールも手軽にリリースできたので、全体像に責任を持つ人がいないのです。私たちなら、見える化、移行、改善の順で解決します。そのほとんどは、すでにお使いのコーディングエージェントを、MCP経由で Brew に接続すれば任せられます。
import_email でインポートできます。インポートは決定論的な処理で、AIを使わず、無料です。React Email と Resend のおかげで、開発者にとってのメールは格段に扱いやすくなりました。型付きのコンポーネントを、プルリクエストでレビューできます。代償が表れるのは、あとになってからです。
メールが散らばっている。コンポーネント、バックグラウンドジョブの中に直接書かれたHTML、サードパーティのダッシュボードにあるテンプレート、別のサービスが代わりに送っているメール。それらをすべて列挙したファイルはありません。
プレビューは本番とは違う。React Email のプレビューサーバーが表示するのは、プレビュー用の props を当てた1つのテンプレートだけです。顧客に実際に届いたものではなく、ほかのテンプレートは何も表示されません。
エンジニアしか見られない。プロダクト、デザイン、サポートの担当者は、エンジニアにプロジェクトを動かしてもらうよう頼まなければならないので、たいていは見ないままです。
リリース後は誰もレビューしない。2年前のメールには、今も古いロゴと、もう販売していないプランが載っています。メールをまとめて見る人がいないので、誰も気づきません。
目標は、すべてのメールを1つの共有ビューで見られるようにすることです。このステップでは、本番環境には一切触れません。

どのフェーズもそれだけで役に立ち、どの段階で止めてもかまいません。
Brew のMCPサーバーは https://brew.new/api/mcp にあり、OAuthを使います。Brew にログインし、エージェントが作業できるブランドを選ぶだけです。Claude Code では、次のように追加します。
claude mcp add --transport http brew "https://brew.new/api/mcp"サーバーを追加しても、保存されるのはこのプロジェクトのローカル設定だけです。すべてのプロジェクトで使うなら、--scope user を付けます。ツールを使う前に、claude mcp login brew を実行するか、/mcp を開いて Brew の認証を済ませてください。
クライアントのセットアップのドキュメントには、Cursor と Codex の手順も載っています。OAuthに対応していないクライアントでは、ブランド単位のAPIキーを使えます。接続したら、エージェントに get_brew_capabilities を呼び出させましょう。そうすれば、エージェントは推測に頼らず、稼働中のツールの一覧を読み取ります。
エージェントに、リストの作成を依頼します。
Find every email this codebase sends. Search for provider calls
(resend.emails.send, postmark, sendgrid, nodemailer, SES), React Email
components and render() calls, and any email templates referenced from
config or a third-party dashboard.
For each email, record: a name, what triggers it, the file and line that
sends it, the template file, the provider, and the variables it uses.
Write the result as a table in EMAILS.md. List anything you could not
resolve under "Unknown" instead of guessing.「Unknown」のセクションは残しておきましょう。認証の確認メールや Stripe の領収書は、コードの中の送信の呼び出しではありませんが、リストには載せるべきものです。EMAILS.md をコミットします。ここで止めたとしても、昨日まではなかったリストが手元に残ります。
インポートに最適な入力は、コードがすでに出力しているHTMLです。React Email の render ユーティリティを使うと、そのHTMLを文字列として取得できます。
import { render } from 'react-email'
import WelcomeEmail from './emails/welcome'
const html = await render(
<WelcomeEmail firstName="Ana" planName="Pro" trialDays={14} />
)古いプロジェクトでは、render を @react-email/render からインポートします。チームがレビューすることになるので、"test" のような値ではなく、現実的なテストデータを使いましょう。すべてのテンプレートをレンダリングしてファイルに書き出すスクリプトは、エージェントに書いてもらいます。
Brew は、format: "jsx" を指定すれば、React Email のモジュールを直接インポートすることもできます。その場合、モジュールは実行されず、静的に読み取られます。props はデフォルト値か PreviewProps の値になり、ほかのローカルファイルからインポートしたコンポーネントは除外されるか拒否され、await などは受け付けられません。単体で完結した1ファイルに収まらない場合は、レンダリングしたHTMLを使いましょう。
次に、レンダリングした各メールを import_email でインポートします。groupName は、プロダクトの領域ごとに1つずつ割り当てます。
For each rendered email in ./email-renders, call import_email with
format "html", the title and subject line from EMAILS.md, and a
groupName by area: "Auth", "Onboarding", "Billing" or "Lifecycle".
Report the emailId for each one and any warnings in its assetReport.インポートによってメールが書き換えられるのではないか、と開発者が気にするのはもっともです。書き換えられることはありません。
assetReport に一覧で表示されます。baseUrl は画像の相対パスを解決するためだけに使われます。グループごとにキャンバスが作られます。brew.new/emails/onboarding を開くと、シーケンス全体が横に並び、デスクトップとモバイルの表示の切り替えや、各デザインへのコメントも使えます。キャンバスのリンクは、あなたの Brew ワークスペースにログインしている人なら開けます。公開用の共有リンクはないので、レビューする人を招待しましょう。
ここで問題が表面化します。ロゴが2種類ある、ボタンのリンクが切れている、フッターが3種類ある、といった具合です。何かを直す前に、audit_email を実行しましょう。リンク、画像、コンプライアンス、読み込みサイズ、クライアントの対応状況、アクセシビリティ、コピーをチェックし、完全な診断1回につき5クレジットを消費します。保存済みの emailId だけでなく、生のHTMLも受け付けるので、インポートしていないメールも診断できます。各チェックとしきい値については、送信前のQAチェックリストで説明しています。
これで、棚卸しのリスト、キャンバス上のすべてのメール、問題の一覧がそろいました。しかも、コードは以前と変わらず、すべてのメールを送り続けています。
ここからは、一部のメールをコードの代わりに Brew が送ります。すべてのメールを移す必要はありません。また、ある種類のメールを送るシステムは、どの時点でも常に1つだけにします。
| 現在のコード | Brew |
|---|---|
resend.emails.send({ react: <WelcomeEmail /> }) |
トリガーイベントと、「メール送信」ステップを1つ持つ公開済みのオートメーション |
| コンポーネントに渡す props | トリガーのペイロードのフィールド。{{ firstName | there }} や {{ trigger.planName }} のような差し込みタグとして使う |
from のドメイン |
Brew で認証済みの送信ドメイン |
| ユーザーのテーブル | REST APIや連携サービスで作成したコンタクト |
| シーケンスを送る cron ジョブ | 「待機」「フィルター」「メール送信」のステップを持つオートメーション |
エージェントは create_domain を呼び出してDNSレコードを取得し、レコードが反映されたら verify_domain を呼び出します。MCPやAPIで作成したドメインは、マーケティング用のドメインになります。パスワードリセットや領収書には、Brew のアプリで、用途を「トランザクション」に設定したドメインを用意しましょう。このドメインでは配信停止のフッターが省かれ、配信停止済みのコンタクトにもメールが届きます。レコードについては、送信ドメインの認証で説明しています。
MCPはコンタクトを更新できますが、作成はできません。コンタクトは、バックエンドから POST /v1/contacts で作成するか、アプリでCSVをアップロードします。このエンドポイントは、1件、または最大1,000件のバッチでコンタクトを作成・更新し、カスタムフィールドも自動で作成します。トリガーを発火させた場合も、ペイロードに含まれるコンタクトが作成または更新されます。インポートするのは、メールの受信に同意した人だけにしましょう。Brew が求める内容は、コンタクトの追加で説明しています。
見落としやすい点があります。インポートしたウェルカムメールには、文字どおり「Hi Ana」と書かれています。テストデータの値を、それぞれ {{ firstName | there }} のような差し込みタグに置き換えましょう。縦棒のあとのテキストが代替値です。Brew のエディターを使うか、置き換えだけに限定して、エージェントに edit_email を実行させます。これはクレジットを消費するAIによる編集なので、プレビューを確認してください。構文の一覧は、差し込みタグのドキュメントにあります。
エージェントは save_trigger でトリガーを作成します。ペイロードのスキーマには、受信者の email を含める必要があります。型付きのペイロード定義を付け(infer_payload_contract がペイロードの例から下書きを作り、set_trigger_contract がそれを保存します)、get_trigger_contract を使って ts 形式か zod 形式で取得しましょう。そうすれば、バックエンドと Brew でペイロードの形がそろいます。
次に save_automation が、トリガーと、メール、ドメイン、件名を設定した「メール送信」ステップを組み立てます。公開するまでは下書きのままです。dryRun はグラフを検証し、test_automation は実際のメールをあなたのアドレスに送ります。check_trigger は、本番で何かを発火させる前に、公開済みのオートメーションがトリガーを待ち受けていることを確認します。
アプリケーションのコードで変更するのは、ここだけです。変更前(Resend):
await resend.emails.send({
from: 'Acme <hello@acme.com>',
to: user.email,
subject: 'Welcome to Acme',
react: <WelcomeEmail firstName={user.firstName} planName={plan.name} />,
})変更後(Brew):
const res = await fetch(
`https://brew.new/api/v1/automations/triggers/${process.env.BREW_WELCOME_TRIGGER_ID}/fire`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BREW_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': `welcome-${user.id}`,
},
body: JSON.stringify({
payload: { email: user.email, firstName: user.firstName, planName: plan.name },
}),
}
)
if (!res.ok) {
// 422 means the automation is still a draft; 400 means the payload failed the schema.
throw new Error(`Brew trigger failed: ${res.status} ${await res.text()}`)
}fetch はリクエストが拒否されても resolve されるので、res.ok をチェックし、再試行やアラートはジョブランナーに任せましょう。エンドポイントは、イベントを受け付けると 202 を、トリガーを待ち受ける公開済みのオートメーションがない場合は 422 を返します。
Idempotency-Key は、ユーザーや注文など、変わらないIDから作ります。同じキーを繰り返し使っても2回送信されることはなく、元の実行が返されます。異なるリクエストボディで同じキーを再利用すると、409 IDEMPOTENCY_CONFLICT が返されます。Brew は、発火に使われたキーを、トリガーの受信記録と同じ期間、つまり90日間保持します。多くのAPIが採用している24時間ではありません。キーは恒久的なものとして扱い、1つの操作に1つのキーを使いましょう。タイムスタンプのキーは再試行のたびに変わってしまいますし、数週間後に別の操作で同じキーを再利用すると、送信されずに、以前のリクエストの再実行として扱われることがあります。
本番のコードは、MCPではなく、APIキーを使ってこのエンドポイントを呼び出します。OAuth接続では、MCPは本番の送信、トリガーの発火、公開のたびに、人に確認を求めます。チャットでは適切な挙動ですが、リクエストハンドラーには向きません。使い分けについては、MCPとREST APIの比較ガイドで説明しています。
EMAILS.md を更新します。Loops 移行ガイドでも、同じパターンを採用しています。
Supabase の確認メールとマジックリンクのメールは、引き続き Supabase から送られます。Brew の Supabase 連携は、ユーザーのイベントを受け取ってオートメーションを開始するもので、Supabase Auth のメールを置き換えるものではありません。送信時に複雑なデータから組み立てる明細付きの請求書も、コードのままのほうがシンプルかもしれません。まずは、差し込みタグに何が必要かを確認しましょう。これらのメールも見える状態を保てるよう、インポートしたコピーをキャンバスに置いておきます。
一式をまとめて診断する。大規模な送信の前に、グループ全体に audit_email を実行し、修正して、もう一度実行します。
実際のクライアントで確認する。test_email_rendering は、ダークモードを含め、特定のメールクライアントやデバイスでのスクリーンショットを返します。1回の実行につき10クレジットです。create_inbox_placement_test は、実際のメールをシード受信トレイに送り、どこに届いたかを報告します。こちらも10クレジットです。
一式に統一感を持たせる。不統一な部分はエディターで直すか、新しいフッターのような1つの変更を、エージェントに edit_email でグループ全体に適用させます。
結果を読む。get_email_analytics は、送信とエンゲージメントを報告します。list_insights は、Brew がアカウントで検出した所見を、それぞれの根拠となる指標とあわせて返します。
パターンを意図的にテストする。キャンバスでパターンを作り、クリックしたかどうかなどの行動にもとづいて、「分岐」ステップでコンタクトを振り分けます。Brew がランダム分割の勝者を選んでくれるわけではないので、パターンの比較は自分で行いましょう。
新しいメールも見える状態に保つ。AGENTS.md などに、次のルールを1行追加しましょう。新しいプロダクトメールは Brew で作成し、トリガーで発火させ、同じプルリクエストで EMAILS.md に追記する、というルールです。
はい。import_email は、レンダリング済みのHTML、MJML、保存した .eml メッセージ、React Email のモジュールを受け付けます。最も確実なのは、レンダリング済みのHTMLです。モジュールは静的に読み取られるため、props はデフォルト値かプレビュー用の値になり、ほかのローカルファイルからインポートしたコンポーネントは除外されるからです。
いいえ。Resend との連携、Resend からのインポート、Resend へのエクスポートはありません。コードがレンダリングしたHTMLをインポートし、移行する場合は、コードから Resend を呼び出す代わりに、Brew のトリガーを発火させます。
いいえ。MCPは、コーディングエージェントからセットアップ、インポート、診断、テストを行うために使いましょう。本番のコードは、APIキーを使ってREST APIを呼び出すべきです。OAuth接続では、本番の送信やトリガーの発火のたびに、MCPが人に確認を求めるからです。
インポートは、メールを編集可能な Brew のデザインに変換し、配信には元のソースのHTMLを使い続けます。スクリプト、iframe、フォーム、イベントハンドラーを取り除き、既存のトラッキングピクセルを無効化し、公開されている画像をホストし直します。Brew から送信する前に、キャンバス上で、デスクトップとモバイルの両方の表示で1通ずつ確認してください。
