Skip to content

listicles

5 Smart Ways to Use Cloudflare R2 in an Email Stack

June 13, 2026

#cloudflare-r2#email-attachments#email-archival#email-backup#own-your-stack
5 Smart Ways to Use Cloudflare R2 in an Email Stack
Visual: Boomerang Betty on okslop

R2 is the quiet workhorse of a Cloudflare email stack. It is object storage with zero egress fees, which turns out to be exactly what email needs: somewhere to put the bytes that do not belong in a database. Here are five ways to use it well.

D1 gets the attention because it holds your messages and contacts, but a real email stack generates a lot of bytes that have no business living in a SQL row. Attachments, inline images, full MIME bodies, backups: all of it wants object storage. R2 is that storage, and its zero egress pricing fits email's habit of fetching the same blob over and over. Five concrete uses, each one keeping your D1 leaner and your stack more yours.

1. Attachments live in R2, not in your database

Put every attachment in R2 and keep only a reference in D1. A PDF invoice, a 4 MB screenshot, an inline logo: none of those belong in a SQLite row. When you stuff blobs into D1, every metadata query and full-text search drags those bytes around with it, and the database bloats fast. The clean split is bytes in R2, metadata in D1: store the object keyed by message ID, and keep the filename, size, and content type as a row that points at it.

This is also what lets the inbox UI fetch an attachment straight from object storage instead of streaming it back through your Worker and database. For the table layout and the key linking the two, see designing an email schema for D1, and for the storage mechanics see storing email attachments in R2.

2. Keep a verbatim raw-MIME copy of every message

Save the original, unparsed MIME of every message as an R2 object the moment it arrives. Parsing is lossy and your parser will change. If you only keep the fields you extracted today, you can never re-derive a header, a missing part, or a quirk you did not think to capture. A raw copy in R2 is your safety net: re-parse it any time your interpreter logic improves, and you have a source of truth that no schema migration can corrupt.

It is also the honest answer to compliance and audit questions. The exact bytes that hit your MX, byte-for-byte, sitting in your own bucket. This pairs directly with handling inbound mail safely, because you store the raw object first and sanitize on the way out. See handling inbound attachments safely for the validation steps before anything reaches a user.

3. Archive cold mail to R2 so D1 stays lean

Move old message bodies out of D1 into R2 once they age past your hot window. D1 should hold the mail you actually touch: recent threads, active contacts, anything search hits often. A two-year-old receipt does not need to sit in your primary database competing for query performance. The pattern is a tiered one: keep the metadata and search text in D1 forever (it is small), but offload the heavy body to R2 once a row crosses an age threshold, fetching it back only on the rare open.

This is how a stack stays fast at year three instead of slowing under its own history. A scheduled Worker sweeps old rows, writes bodies to a cold-storage bucket, and updates the D1 row to point at the object. The full tiering strategy, thresholds, and rehydration path are in archiving email to R2 cold storage.

4. Use R2 as the backup target for the whole stack

Snapshot D1 on a schedule and write the dump to R2, next to the raw MIME you already keep there. Owning your stack means owning your recovery story, and R2 is the natural place to land backups: same account, same billing, no egress tax for moving data in. A cron-triggered Worker exports your D1 tables, writes a timestamped object to a backup bucket, and you keep versioning on so a bad migration does not erase your only good copy.

The deeper win is that backup and archive reinforce each other. Because every message's original bytes already live in R2 verbatim (use #2), you can rebuild a corrupted or lost D1 search index by re-parsing the raw objects, not just restore from a snapshot. That is two independent recovery paths from one storage layer. The snapshot cadence and restore drill are covered in backing up and restoring your email stack.

5. Lean on zero egress, because email re-fetches everything

R2's zero egress fees are not a footnote for email, they are the reason it fits. Email is unusually re-read: an attachment gets fetched when the recipient opens it, again when it is forwarded, again when it is re-sent, again when an agent or interpreter pulls it for context. On most object stores every one of those fetches is a metered byte leaving the building. On R2, egress is free, so the cost of a heavily-accessed attachment or a frequently-rehydrated archive stays flat.

That changes what you are willing to do. You can serve attachments directly to the inbox, let an AI agent retrieve raw bodies for context, and re-download archives during a re-index without watching a bandwidth meter. Storage and operations still cost, so this is not free storage, but the pricing shape happens to match email's access pattern. It is also a concrete piece of owning your email data: the bytes sit in your bucket, on your account, fetchable on your terms rather than a vendor's.

Where R2 ends

Object storage is the right tool here precisely because it is dumb and durable. R2 will not search your mail (that is D1 and FTS5), it will not run your logic (that is Workers), and it will not de-duplicate or thread anything for you. Its job is to hold bytes cheaply and hand them back without an egress bill, and it does that one job well enough that the rest of the stack can stay lean. R2 is one of the Cloudflare primitives every email stack is built on, and it sits alongside the other pieces in the serverless email toolkit for Cloudflare.

To be fair to the alternatives: a managed email SaaS hides all of this, and if you never want to think about where an attachment physically sits, that convenience is real. The difference is ownership. With R2 the attachments, the archives, and the backups are objects on your own Cloudflare account, not an export you file a ticket to retrieve.

Deploy your own email stack: npx tinbox init <domain>. It wires R2 into the attachment, archive, and backup paths for you on your own Cloudflare account, so the bytes and your mail stay yours.

Frequently asked questions

How should I use Cloudflare R2 for email attachments and archival?

Put the bytes in R2 and the metadata in D1. Each attachment becomes an R2 object keyed by message ID, with the filename, size, and content type stored as a row in D1. For archival, keep a verbatim raw-MIME copy of every message in R2 from the moment it arrives, and move cold message bodies out of D1 into R2 once they age past your hot window. R2's zero egress fees make this cheap because email attachments get fetched and re-sent constantly.

Should email attachments go in D1 or R2?

R2. D1 is SQLite and you want its rows small so search and metadata queries stay fast. Large blobs like attachments, inline images, and full MIME bodies belong in R2 as objects, with a key column in D1 linking the row to the object. This keeps D1 lean and lets the inbox UI fetch attachments straight from object storage.

Does Cloudflare R2 charge for downloading email attachments?

No. R2 has zero egress fees, so serving an attachment to the inbox, re-sending it, or re-downloading a raw MIME file does not incur bandwidth charges. You pay for storage and operations, not for data leaving R2. That is the main reason R2 fits email, where the same attachment can be fetched many times.

How do I back up a Cloudflare email stack?

Snapshot D1 (or export its tables) on a schedule and write the dump to an R2 bucket, alongside the raw-MIME objects you already keep there. Because the original bytes of every message live in R2 verbatim, you can rebuild the searchable D1 index by re-parsing them if a row is ever lost. Keep backups in a separate bucket with versioning for a clean restore path.

Will storing email in R2 keep my data on my own account?

Yes. When you run the stack on your own Cloudflare account, the R2 buckets, the D1 database, and the Workers all sit under your billing and your access controls. Your attachments and archives are objects you own, not an export you have to request from a SaaS vendor.