Back to prompts
✦ClaudeProduct

Migrate transactional email to Cloudflare Email

levelsio's full agent prompt to audit and migrate transactional email to Cloudflare Email Service.

Loading the original X post…

Open original on X

levelsio's full agent prompt to audit and migrate transactional email to Cloudflare Email Service.

The prompt

✦Claude Code / Cursor
1# Prompt: Migrate transactional email to Cloudflare Email Service
2
3Paste this into Claude Code (or Cursor, or any agent) running inside your project.
4
5---
6
7I want to migrate this codebase's outbound email from its current provider (Postmark / SES / Resend / SendGrid / Mailgun / etc.) to Cloudflare Email Service (public beta, launched April 2026). Help me do this carefully.
8
9## Context: what Cloudflare Email Service is
10
11A new transactional email API from Cloudflare. Endpoint:
12
13```
14POST https://api.cloudflare .com/client/v4/accounts/{ACCOUNT_ID}/email/sending/send
15Authorization: Bearer {API_TOKEN}
16Content-Type: application/json
17```
18
19Request body:
20
21```json
22{
23 "to": "user@example.com", // string OR array of strings
24 "from": "no-reply@yourdomain.com", // string OR {"address":"x@y","name":"Display"}
25 "subject": "...",
26 "html": "<p>...</p>", // optional
27 "text": "...", // optional (one of html/text required)
28 "cc": ["..."], // optional, array
29 "bcc": ["..."], // optional, array
30 "reply_to": "...", // optional, single string
31 "headers": {"List-Unsubscribe": "<...>"} // optional, e.g. for newsletters
32}
33```
34
35Success response: HTTP 200 + `{"success":true,"result":{"delivered":[],"queued":[],"permanent_bounces":[]}}`.
36
37Failure: non-200 OR `success:false` OR non-empty `permanent_bounces`. Always check all three.
38
39Pricing: $5/mo Workers Paid plan + 3,000 emails free + $0.35 per 1k after. Roughly 5× cheaper than Postmark. No batch send endpoint — loop single sends.
40
41## Steps you should follow
42
43### 1. Verify prerequisites with me
44
45Before writing any code, ask me to confirm:
46
47- I have a Cloudflare Workers Paid plan ($5/mo)
48- I've onboarded my sender domain(s) in Cloudflare dashboard → Email → Email Sending → Onboard Domain (this auto-adds SPF/DKIM/DMARC + cf-bounce MX records)
49- I have an API token with `email_sending:write` scope (created at http://dash.cloudflare.com/profile/api-tokens → Custom Token)
50- I have my Cloudflare account ID
51
52Don't proceed until you have these.
53
54### 2. Recommend a domain reputation strategy
55
56Most apps should split senders across 2-3 subdomains so spam complaints on one don't drag down deliverability on others:
57
58- `mail.<domain>` or `members.<domain>` → transactional (login, receipts, password reset, in-app notifications)
59- `e.<domain>` → cold/recovery (abandoned cart, win-back campaigns)
60- `newsletter.<domain>` → opt-in newsletters with List-Unsubscribe headers
61
62Each subdomain needs to be onboarded separately in Cloudflare. Ask me which I want.
63
64### 3. Audit existing email sends
65
66Use grep/search to find every place in this codebase that sends email. Look for:
67
68- The current provider's SDK class names, API URLs, env/config vars
69- Generic patterns like `mail()`, SMTP usage, `nodemailer`, etc.
70
71Group findings by email type/purpose (e.g. "magic-link login", "payment receipt", "weekly newsletter") rather than by file. Tell me what you found before changing anything.
72
73### 4. Add a single helper function
74
75Don't sprinkle Cloudflare API calls across the codebase. Add one helper (provider-specific name like `sendEmailViaCloudflare()`) that:
76
77- Defaults `from` from a config var (don't hardcode)
78- Parses `"Name <email@domain>"` strings into the API's `{address, name}` object form
79- Accepts `cc`/`bcc` as either string or array
80- Accepts a `headers` dict (newsletters need `List-Unsubscribe` + `List-Unsubscribe-Post`)
81- Returns `bool` (true on success, false on any failure)
82- On failure, logs/alerts somewhere I can see (Telegram, Sentry, log file — match what the codebase already does)
83- Sets curl/fetch timeouts (5s connect, 15s total) so a stuck CF API can't hang the request
84- Treats `permanent_bounces: [...]` non-empty as a soft failure
85
86### 5. Migrate one low-stakes email type first
87
88Don't migrate everything at once. Pick the lowest-stakes email type in the audit (something where landing in spam wouldn't lose me money or users — e.g. "internal admin alert", "profile photo rejection") and migrate just that one. Test it end-to-end. Confirm the email actually arrives. Only then propose the next migration.
89
90### 6. Stop me from migrating login email yet
91
92If my codebase sends magic-link login or password-reset emails, do not migrate those to Cloudflare yet. Cloudflare Email Service is brand new (~1 month old at writing). Its IP/domain reputation is unproven. Login emails landing in spam = users locked out. Keep those on the current provider until at least 3 months of clean deliverability data on the lower-stakes types. Tell me this explicitly.
93
94### 7. Suggest commit boundaries
95
96After each successful migration, suggest a focused git commit with a clear message. Don't bundle unrelated changes.
97
98## Important caveats to surface to me
99
100- Beta product. Pricing isn't fully finalized. SLA undefined. Could change.
101- No batch endpoint. Mass sends (newsletters to 1000+ recipients) need a loop — at ~150ms/send that's ~2.5min per 1000. Fine for crons, bad for sync user-facing flows.
102- No bounce webhooks yet. Surface failures via the response body's `permanent_bounces` array.
103- Suppression list auto-managed. Hard bounces, repeated soft bounces, and spam complaints get blocked. Spam-complaint suppressions are hard to remove (anti-abuse).
104- No per-message logs/dashboard yet. Use the response's `messageId` for tracking if I need it.
105- List-Unsubscribe headers are passed through verbatim — Gmail's bulk-sender requirement still met, but only if I include them in `headers`.
106
107## Your first action
108
109Before writing any code: do step 1 (ask for prerequisites) and step 3 (audit existing sends), then propose the migration order with a brief explanation of the reasoning. Wait for my confirmation before making changes.

What this prompt is good for

Landing pages

Create stunning product landing pages

SaaS websites

Modern SaaS marketing websites

Product launches

Launch pages for new products

Startup websites

Complete websites for early-stage startups