Install
$ agentstack add skill-yigitkonur-skills-by-yigitkonur-build-cloudflare-email-sending ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README — it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming — see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps — measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Build Cloudflare Email Sending
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.
When to use
Use this skill if you are:
- Migrating transactional email off Resend, SES, Postmark, or SendGrid onto
Cloudflare.
- Fixing Supabase Auth's rate-limited built-in mailer, or fully rebranding
its signup/reset/magic-link emails.
- Wiring a
send_emailWorkers binding,wrangler email sending, or a
Worker relay for a backend that runs outside Cloudflare (Railway, Fly, Vercel, a VPS).
- Architecting transactional email from scratch for a stack already on
Workers.
- Debugging
cf-bounceDNS, 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.
First: three facts that shape every decision
- The sending domain's DNS zone must be on Cloudflare. Onboarding
auto-creates bounce MX + SPF + DKIM + DMARC records; it cannot do that on external DNS. If the target domain's nameservers point elsewhere, either move the zone first or onboard a different Cloudflare-hosted domain as an interim sender (working substitute now beats perfect sender later — swap from when the zone moves).
- **API-token creation is dashboard-gated; the Worker binding needs no token
at all.** For backends running outside Cloudflare, the highest-leverage pattern is a tiny Worker relay: the Worker holds the send_email binding ambiently, the backend calls it over HTTPS with a shared random secret. No Cloudflare credential ever touches the backend.
- Quota starts at ~1,000 sends/day and Cloudflare raises it reluctantly
(practitioner reports: a $20-30k/mo account fought to get 5,000/day). 3,000 sends/month included on Workers Paid ($5/mo floor), then $0.35/1k. If projected volume exceeds 1,000/day, file the limit-increase form at the start of the migration, not the end — treat it as a blocking dependency.
Workflow
1. Verify tooling and account
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= before every wrangler email command. Find the account that owns the target zone by probing:
CLOUDFLARE_ACCOUNT_ID= wrangler email sending settings
# "Could not find a zone" → wrong account; repeat with the next id
2. Onboard the sending domain
dig +short NS # *.ns.cloudflare.com → zone is on Cloudflare; else see fact 1
CLOUDFLARE_ACCOUNT_ID= wrangler email sending enable
CLOUDFLARE_ACCOUNT_ID= wrangler email sending dns get # 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.. Verify propagation before the first send:
dig +short MX cf-bounce.
dig +short TXT cf-bounce._domainkey. | head -c 40
dig +short TXT _dmarc.
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.) onboard independently — use one per traffic class for reputation isolation.
3. Pick the send path
| 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= npx wrangler deploy
openssl rand -hex 32 | CLOUDFLARE_ACCOUNT_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 ").
4. Wire the application with a fallback window
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:
- Migrating from Resend (or SES/Postmark — same shape): read
references/migrate-from-resend.md
- Replacing Supabase Auth's built-in mailer (confirmation, magic link,
recovery emails): read references/supabase-auth-emails.md
- Greenfield — architecting email for a new product on Cloudflare:
read references/greenfield-architecture.md
5. Verify end-to-end, then clean up
Every 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:///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:///send -H "Authorization: Bearer $EMAIL_SENDER_TOKEN" \
-H "content-type: application/json" \
-d '{"from":{"address":"welcome@","name":"Product"},"to":"","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//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.
Credential hygiene (non-negotiable)
- The Worker
AUTH_TOKENlives in exactly two places:wrangler secretand
the backend's secret store. Never in git, wrangler.jsonc, docs, or chat.
- Rotation is a paired operation:
wrangler secret put AUTH_TOKEN+ update
the backend var in the same change window.
- SMTP-path tokens are real Cloudflare API tokens — scope to **Email
Sending: Edit** only, nothing broader.
- When writing examples or reports, show
openssl rand -hex 32as the
generation step, never a literal value.
Failure quick-reference
| Symptom | Cause | Fix | |---|---|---| | wrangler email sending → 404 | wrangler | | "Incorrect type for the 'email' field" | REST shape sent to the binding | {email} for binding, {address} for REST | | ESENDERNOTVERIFIED | domain/subdomain not onboarded | wrangler email sending enable + DNS check | | 429 / ERATELIMITEXCEEDED | 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 messageid`, not that panel |
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: yigitkonur
- Source: yigitkonur/skills-by-yigitkonur
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.