Skip to content

listicles

5 Cloudflare Primitives Every Email Stack Is Built On

June 8, 2026

#cloudflare#email-architecture#d1#r2#queues
5 Cloudflare Primitives Every Email Stack Is Built On
Visual: Nyx Datacenter on okslop

An email stack sounds like a monolith. On Cloudflare it is five small primitives, each doing exactly one job. Learn what each one is for and the whole architecture stops being mysterious.

People assume "build your own email" means a mail server, a cron box, a Postgres instance, and a weekend you will not get back. On Cloudflare it decomposes into five primitives that map cleanly onto the five jobs an email stack actually has: run code, store data, hold blobs, queue work, and receive mail. Here is each one, what it does, and where it ends.

1. Workers: the compute that runs everything

Workers are the engine room. Every action in an email stack is a function call: render a template, sign a message, write a row, fire a webhook, run a nightly digest. Workers handle all of it at the edge with no cold starts and no server to patch, so a POST /send and a 2 a.m. cron sweep run on the same runtime.

The one hard limit worth tattooing on your wrist: Workers cannot open outbound port 25 for direct-to-MX SMTP. The final delivery hop leaves over HTTPS through a sending API or relay, not a TCP connection to port 25. Everything before that hop (validation, queuing, DKIM signing, logging) is Worker territory. If you are wiring a send path from scratch, see how to actually send email from a Worker.

2. D1: the SQLite database that is your source of truth

D1 is where the mail lives. It is SQLite at the edge, and it holds your messages, threads, contacts, lists, and send logs. Because it is real SQL, you get joins, transactions, and (the part people sleep on) FTS5 full-text search without bolting on a separate search service. Your inbox search and your WHERE list_id = ? query hit the same database.

The schema is the part worth getting right early: index for your common access patterns, keep large blobs out of the row, and watch the correlated-subquery behavior that bites people doing counts. For the table-by-table version, see designing an email schema for D1. D1 is also why the whole stack stays on your account: the contact list is a table you own, not an export you request.

3. R2: object storage for attachments, MIME, and archives

R2 holds everything too big or too raw for a database row. Attachments, inline images, and the full original MIME of every message belong in object storage, not in D1. R2's headline feature for email is zero egress fees, which matters because email is full of attachments that get fetched, re-sent, and re-downloaded.

Three jobs R2 does well: serving attachments to the inbox UI, keeping a verbatim raw-MIME copy of each message for compliance and re-parsing, and acting as cold storage so D1 stays lean as old mail ages out. The pattern is simple: metadata and search text in D1, bytes in R2, a key linking them. The mechanics are in storing email attachments in R2.

4. Queues: the buffer that makes sending reliable

Queues are what separate a toy sender from a real one. Sending one transactional email inline is easy. Sending fifty thousand newsletter emails, surviving a relay hiccup, and not double-sending on a retry is a queue problem. A Worker enqueues send jobs and returns immediately; a consumer Worker drains the queue in batches, retries failures, and pushes poison messages to a dead-letter queue instead of dropping them.

This is also how you respect rate limits and keep transactional mail fast while a bulk campaign grinds through the background. The design choices (batch size, idempotency keys, retry policy) are the difference between "mostly delivered" and "delivered once, every time." See using Cloudflare Queues for email sending for the consumer patterns. When you outgrow basic queue rate limiting, Durable Objects add per-tenant coordination and single-threaded scheduling, though most stacks do not need them on day one.

5. Email Routing: the inbound path that receives mail

Email Routing is the front door for incoming mail. Point your domain's MX records at Cloudflare, and Email Routing accepts mail for your addresses and hands each message to a Worker as a parsed event. No mail server, no IMAP daemon, no port 25 to defend. The Worker decides what happens next: store it in D1, drop the attachment in R2, run an interpreter, fire a webhook, or thread it into a reply.

This is the primitive that turns a send-only setup into a real two-way inbox, and it is the piece most "serverless email" tutorials skip. For the receiving side end to end (MX records, the email handler, parsing), start with the Cloudflare Email Routing walkthrough.

How the five fit together

Read top to bottom and the data flow falls out on its own. Inbound mail arrives through Email Routing, gets handled by a Worker, which writes metadata to D1 and the raw bytes to R2. Outbound mail goes the other way: a Worker enqueues to Queues, a consumer drains it through the HTTPS delivery hop, and logs the result back to D1. Search, contacts, and the inbox UI all read from the same D1 tables. Five primitives, two directions, one account.

That is the honest appeal of the Cloudflare email architecture: it is not a black box you rent, it is five well-understood services you assemble. The trade is real, and worth naming. You own DNS authentication, deliverability, and bounce handling yourself instead of paying a SaaS to hide them. Whether that is a feature or a burden depends on your team, and whether a Cloudflare email stack is production ready digs into exactly where that line sits. If you are still deciding which compute layer to assemble them on, Pages versus Workers for an email app settles that choice first.

If you want the assembled version rather than five tutorials, that is what we build. Deploy your own email stack: npx tinbox init <domain>. It wires these five primitives together on your own Cloudflare account, so your data and mail stay yours.

Frequently asked questions

Which Cloudflare services do I need to build an email stack?

Five primitives cover it: Workers for compute, D1 (SQLite) for storing messages and contacts, R2 for attachments and archives, Queues for reliable async sending, and Email Routing for receiving inbound mail. Workers and Email Routing have generous free tiers; D1, R2, and Queues are metered but cheap at typical email volumes. Note that Workers cannot open outbound port 25 for direct-to-MX SMTP, so the actual final-hop delivery still goes through an HTTP-based relay or sending API.

Can Cloudflare Workers send email directly over SMTP?

Workers cannot perform direct-to-MX SMTP because outbound port 25 is blocked. They can use the native Email Service binding, an HTTPS sending API, or outbound TCP to an allowed third-party submission service. Workers still handle rendering, queuing, policy, and logging around that handoff.

Do I need Durable Objects for an email stack?

Not for a basic stack. The five core primitives (Workers, D1, R2, Queues, Email Routing) are enough to send, receive, store, and search mail. Durable Objects become useful for stricter rate limiting, per-tenant coordination, or single-threaded scheduling once you scale.

Is Cloudflare D1 enough to store all my email?

For most workloads, yes. D1 holds message metadata, bodies, contacts, and lists, with FTS5 full-text search built in. You offload large attachments and raw MIME to R2 to keep D1 lean, and archive cold mail to R2 once a row ages out.

Is a Cloudflare email stack production ready?

The primitives are production-grade and used at scale, but you own deliverability, DNS authentication, and bounce handling yourself rather than renting them. Whether that trade is right depends on your team's appetite for owning the stack versus paying a SaaS to manage it.