Bring-your-own-storage

Email hosting on your own S3 bucket: how the storage works

A message is verified, encrypted per mailbox, then written into your own bucket Sender Our MX receives SPF · DKIM · DMARC Encrypted per mailbox Your bucket S3 R2 WebDAV Git Your account, region and bill

A message arrives for you, and about a second later there is a new object in a bucket on your own cloud account. That object is the interesting part of bring-your-own-storage email: what it contains, how it got written, and what you can do with it that no mail provider would ever let you do to their disks.

What actually ends up in your bucket?

Encrypted blobs, plus the index that makes them a mailbox. Each message is compressed with zstd, encrypted with a per-mailbox key using age (the X25519-based file encryption format), and written under a blobs/ prefix with a name derived from a hash of its contents. Identical bytes get the same name. Nothing in the object key tells a reader what it holds — not the subject, not the sender, not which address it was addressed to.

The index is the part people forget when they imagine this working. A mailbox is not a pile of blobs: something has to know which blob is which message, what thread it belongs to, which are unread, which are labelled. That structure is written into the same storage and encrypted the same way. It is why “my mail is in my bucket” is a stronger claim than it first sounds — the archive is not merely retrievable, it is self-describing to anything holding the key.

One question to ask any provider making this offer, before you commit a domain to it: is the on-storage layout documented? Bytes you can only read through one vendor’s software are a weaker form of ownership than they look.

How does a message get from the sender into your storage?

Five steps, and the order of the last two is what matters.

Our MX host answers the SMTP connection and checks the message: SPF, DKIM and DMARC on the inbound mail, sender reputation and blocklist checks, then spam and phishing analysis. Filtering has to read plaintext, so it all happens here, before anything is encrypted at rest. A service that filters your spam has seen your mail; anyone claiming both is describing something that cannot work.

Then the recipient address is resolved to a mailbox, the message is compressed and encrypted with that mailbox’s key, and the object is written into your storage — a PUT to S3 or an S3-compatible endpoint, R2, a WebDAV server, or a commit to a Git repository.

Only after the write succeeds does the edge answer 250 OK. The receiving edge is stateless and keeps no queue of its own, which sounds like a limitation and is actually the reliability guarantee: it cannot acknowledge a message it has not stored. When your storage is down, the message is still sitting in the sending server’s queue, which is the most durable place it could possibly be.

What can you do with the bucket that a provider would never allow?

Everything the storage API offers, because it is your bucket and the mail service is just a client with a key.

Object versioning turns “something deleted a year of mail” into a recovery rather than an incident. Lifecycle rules cut the bill on an archive nobody reads. Cross-region replication or a nightly rclone sync puts a second copy at a second operator in a second country, on the same schedule as everything else you back up. Object Lock satisfies a retention mandate your mail provider has never heard of. Bucket access logs record every read, including ours. Server-side encryption with your own KMS key wraps our ciphertext in a second layer whose key you can revoke unilaterally.

Two of those have sharp edges. Cold-tier transitions make retrieval slow, so point them at a replica and not at the blobs your webmail is actively reading. And Object Lock in compliance mode means nobody deletes an object before its retention date — including you, including the version of you who set the retention period wrong. That mode is a decision, not a checkbox.

The other freedom is placement. You pick the region, and separately you pick who operates it. Region is not jurisdiction: an EU region run by a US-incorporated company is still reachable under the CLOUD Act, in force since 2018. Choosing both, deliberately, is most of the reason people want this arrangement.

Cost behaves differently from what the pricing pages train you to expect. Mail is thousands of small objects, so per-request charges and egress dominate, not the per-gigabyte rate. R2 bills no egress at all.

What stays with the routing service?

The parts that must live on the public internet, and — by default — the keys.

We keep the MX records, TLS on the edge, DKIM signing and DMARC alignment for your outbound mail, the IP reputation work, the spam and phishing analysis, and the webmail client. Your bucket credentials sit sealed in our database, because delivery needs them.

Key custody is the honest asterisk. On the default server-custody tier we hold per-mailbox keys, which is what lets the mailbox render and search in a browser; your storage provider sees only ciphertext, but we can decrypt. The device-custody tier, in beta, generates keys in your browser and erases our copy — from then on the service can write into your storage and cannot read what it wrote. Both are defensible designs. Only one of them is “nobody but me”, and the difference is worth knowing before you quote it in a policy document.

How does this fail?

Three ways, in the order they actually happen.

Credentials. You rotate an access key on a Tuesday and forget the mail path is a consumer of that bucket. Delivery starts failing, and because the edge withholds 250 OK, nothing is lost immediately — you get a delay and warning bounces, not a hole in your archive. Rotate the way you would for any other client: add the new key to the mailbox first, revoke the old one after.

Policy. A bucket policy that turns out to be public exposes ciphertext rather than messages, which is the difference between an embarrassment and a catastrophe — but object counts, sizes and timestamps are still a traffic-analysis dataset, and a mailbox you deliberately set to plaintext at rest exposes exactly what you would expect. A lifecycle rule with an expiration action deletes mail on schedule and does not ask twice. A KMS key policy that omits the writer stops delivery cold.

Yourself. Custody transfers responsibility along with power. Deleting the bucket deletes the mail, and there is no support ticket that restores a backup we never had. That is not a gap in the product. It is the thing you bought.

The test I would apply to any provider offering this: can you revoke its storage credentials in ten seconds, and is your archive still readable afterwards? If yes, you own your mail. If the answer involves an export tool, you own an account.

Frequently asked questions

Can email really be stored in an S3 bucket?

Yes. The receiving mail service compresses each message, encrypts it with a per-mailbox key and writes it as an object in your bucket, alongside index data that turns those objects back into a mailbox. Nisdos Mail does this against Amazon S3, any S3-compatible endpoint (Cloudflare R2, Backblaze B2, Wasabi, Storj, Scaleway, MinIO), a WebDAV server, or a Git repository.

Who can read email stored in my own S3 bucket?

Whoever holds the encryption key, which is a separate question from who holds the bytes. On Nisdos Mail's default server-custody tier the per-mailbox keys stay with us so the mailbox can be rendered and searched in a browser; on the device-custody tier (in beta) keys are generated in your browser and our copy is erased, after which we can write into your storage but not read what we wrote.

What happens if my bucket is unreachable when mail arrives?

The receiving edge does not answer 250 OK until the message is stored, so the sending server keeps it in its own queue and retries on the normal SMTP schedule. Postfix's default queue lifetime is five days, which is the window you have to fix credentials or connectivity before senders start giving up.

How much does it cost to store email in S3?

Less than the per-gigabyte headline suggests and more than you expect, because mail is a small-object workload: request charges and egress usually dominate the bill. Cloudflare R2 charges no egress fee, which is why it comes up so often here. If you would rather not run a bucket, Nisdos Mail hosts the storage at $2 per 25 GB per month, plus $1 per 25 GB to keep it in the EU.

Can lifecycle rules move old email to cold storage?

They can, and you should be careful where you point them. Deep-archive classes such as Glacier Flexible Retrieval restore in minutes to hours, so applying a transition to the blobs your live mailbox still reads makes your own mailbox slow. Apply cold tiering to a replicated second copy instead.