build-cloudflare-email-sending
Use if sending email via Cloudflare Email Service, replacing Resend/SES/Postmark or send_email.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Use if sending email via Cloudflare Email Service, replacing Resend/SES/Postmark or send_email.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Use if driving agent-browser for webpage interaction, screenshots, @ref snapshots, tabs, UI verification, CDP attach, Steel Browser, or cloud providers (Browser Use, Browserbase, Browserless, Kernel).
Use if verifying claimed-done work or auditing session/plan/branch completion with evidence.
Use if creating, redesigning, or merging a Claude skill, with research before writing SKILL.md.
Use skill if you are running repeatable Codex reviews across lenses or branches, optionally verifying and fixing confirmed findings in isolated worktrees.
Use if running deep multi-file research over 5+ entities or a market — wave-dispatched corpus.
Use if finishing a project — review and merge every branch/worktree into main, retire dead branches.
| name | build-cloudflare-email-sending |
| description | Use if sending email via Cloudflare Email Service, replacing Resend/SES/Postmark or send_email. |
Cloudflare Email Service (open beta since 2026-04) sends transactional email
through three equivalent paths — a Workers send_email binding, a REST API,
and SMTP — all sharing one pipeline, one quota, and automatic DKIM/ARC
signing. This skill gets a sending domain onboarded, picks the right send
path for the caller's architecture, and executes migrations from other
providers without breaking email mid-cutover.
Use this skill if you are:
send_email Workers binding, wrangler email sending, or a
Worker relay for a backend that runs outside Cloudflare (Railway, Fly,
Vercel, a VPS).cf-bounce DNS, DKIM/SPF conflicts, or Email Service quota
errors.This is the sending product (open beta, 2026) — not Email Routing/receiving, not the retired MailChannels path.
from when the zone moves).send_email binding
ambiently, the backend calls it over HTTPS with a shared random secret.
No Cloudflare credential ever touches the backend.wrangler --version # need >= 4.110; older versions 404 on sending endpoints
wrangler whoami # confirms login; note available account IDs
Multi-account logins fail non-interactively — export
CLOUDFLARE_ACCOUNT_ID=<id> before every wrangler email command. Find the
account that owns the target zone by probing:
CLOUDFLARE_ACCOUNT_ID=<id> wrangler email sending settings <domain>
# "Could not find a zone" → wrong account; repeat with the next id
dig +short NS <domain> # *.ns.cloudflare.com → zone is on Cloudflare; else see fact 1
CLOUDFLARE_ACCOUNT_ID=<id> wrangler email sending enable <domain>
CLOUDFLARE_ACCOUNT_ID=<id> wrangler email sending dns get <domain> # inspect what was created
enable auto-creates, on the cf-bounce subdomain: three bounce MX records,
an SPF TXT, a DKIM key at cf-bounce._domainkey, plus _dmarc.<domain>.
Verify propagation before the first send:
dig +short MX cf-bounce.<domain>
dig +short TXT cf-bounce._domainkey.<domain> | head -c 40
dig +short TXT _dmarc.<domain>
SPF conflict rule: if the root domain already has an SPF record from
another sender, Cloudflare's include:_spf.mx.cloudflare.net must be merged
into that one record — a second SPF TXT is invalid and tanks deliverability.
The cf-bounce records are namespaced and never conflict. Subdomains
(notifications.<domain>) onboard independently — use one per traffic class
for reputation isolation.
| Caller | Path | Why |
|---|---|---|
| Code already on Workers | send_email binding | Zero credentials, zero HTTP hop |
| Backend outside Cloudflare (Railway/Fly/VPS/Vercel functions) | Worker relay (this skill's template) | Token creation is dashboard-gated; a shared secret to your own Worker is the only credential |
| Legacy app that only speaks SMTP | smtp.mx.cloudflare.net:465, user api_token, password = a token with Email Sending: Edit | No code changes |
| One-off / CI / smoke test | wrangler email sending send --from ... --to ... --subject ... --text ... | Fastest proof |
For the Worker relay: copy assets/email-sender-worker/ into the repo (e.g.
workers/email-sender/), deploy, set the secret, verify:
cd workers/email-sender
CLOUDFLARE_ACCOUNT_ID=<id> npx wrangler deploy
openssl rand -hex 32 | CLOUDFLARE_ACCOUNT_ID=<id> npx wrangler secret put AUTH_TOKEN
# keep the generated value ONLY in the backend's secret store (env var / vault) — never in git
The #1 API landmine: the Workers binding's address-object shape is
{email, name} while the REST API's is {address, name}. The bundled
Worker template translates; hand-rolled code that passes {address} to the
binding fails with "Incorrect type for the 'email' field". The wrangler CLI
smoke-send accepts display strings ("Name <addr@dom>").
Never hard-cut from the old provider. Route by config: prefer Cloudflare when its env vars are set, fall back to the old provider otherwise — either provider alone keeps email alive, and rollback is unsetting two vars. Keep the old provider's fallback for 2–4 weeks of observed deliverability (the product is beta; no public inbox-placement history exists yet).
Provider-specific mappings, DNS specifics, and code-shape diffs:
references/migrate-from-resend.mdreferences/supabase-auth-emails.mdreferences/greenfield-architecture.mdEvery leg gets a live probe — a green deploy is not a delivered email:
# 1 Worker auth gate: wrong token must 401, missing config must 503
curl -s -X POST https://<worker>/send -H "Authorization: Bearer wrong" -d '{}' -o /dev/null -w "%{http_code}\n"
# 2 Real send returns a message_id
curl -s -X POST https://<worker>/send -H "Authorization: Bearer $EMAIL_SENDER_TOKEN" \
-H "content-type: application/json" \
-d '{"from":{"address":"welcome@<domain>","name":"Product"},"to":"<real inbox you control>","subject":"cutover probe","text":"probe"}'
# 3 The email actually lands in that inbox (check spam folder too)
# 4 The OLD provider's send log shows nothing new → traffic really moved
# 5 Quota headroom: GET /accounts/<id>/email/sending/limits (or the dashboard)
Delete any test users/rows the probes created. If probes sent to fake
addresses, expect them in permanent_bounces — repeated hard bounces
auto-suppress recipients, and unsuppression requires a Cloudflare support
case, so always probe with a real inbox you control.
AUTH_TOKEN lives in exactly two places: wrangler secret and
the backend's secret store. Never in git, wrangler.jsonc, docs, or chat.wrangler secret put AUTH_TOKEN + update
the backend var in the same change window.openssl rand -hex 32 as the
generation step, never a literal value.| Symptom | Cause | Fix |
|---|---|---|
wrangler email sending → 404 | wrangler < 4.110 | npm i -g wrangler@latest |
| "More than one account available" | multi-account login | CLOUDFLARE_ACCOUNT_ID=<id> |
| "Incorrect type for the 'email' field" | REST shape sent to the binding | {email} for binding, {address} for REST |
E_SENDER_NOT_VERIFIED | domain/subdomain not onboarded | wrangler email sending enable + DNS check |
429 / E_RATE_LIMIT_EXCEEDED | daily quota (default ~1,000) | limit-increase form; queue + retry with backoff |
| Lands in spam | DMARC alignment missing | verify _dmarc TXT exists; merged SPF on root |
| Recipient silently gets nothing | auto-suppressed after hard bounces | Cloudflare support case; stop probing fake addresses |
| Dashboard shows sends as "dropped" | known Email Routing metrics false-negative | trust the send response's message_id, not that panel |