Design an Email Service

A billion users, forty messages a day each, kept forever — email is a storage problem wearing a messaging problem’s clothes, and almost none of it is under your control once a message leaves the building. Built from zero: a commit-first envelope that lands on the yearly petabytes, the send and receive pipelines threaded through queues so a slow recipient never blocks anyone, the mailbox data model at the heart — partition by user, folders, time-ordered message IDs, and the honest read/unread denormalization a wide-column store forces on you (two tables, move the row) — the search index as its own write-heavy LSM, and deliverability as its own discipline: IP reputation, dedicated-IP warm-up, SPF/DKIM/DMARC in a breath each, and the bounce/complaint feedback loops — then the failure sweep whose sharpest box is your own IP landing on a blocklist.

System design · Systems. The source ↗

A free, interactive, animated visual explainer of Design an Email Service — built to be understood, not skimmed.

Questions

Why is designing an email service mostly a storage problem?
Because the volume is enormous and email is kept indefinitely. A billion users sending forty messages a day is forty billion messages daily, and at an average of around 50 KB each — most of the weight is attachments — that is roughly two petabytes of new data every day, over 700 petabytes a year in a single copy, and you replicate it. Unlike a message queue, which retains a bounded window and ages data out, a mailbox is forever: people expect a ten-year-old email to still be there. So the defining challenge isn’t the SMTP plumbing that moves a message — that’s a solved, well-standardized problem — it’s storing, indexing, and serving an ever-growing mountain of mailbox data cheaply, with fast per-user reads. The attachments, which dominate the bytes, are pushed into blob storage and deduplicated, while the metadata (who, when, subject, folder, read state) lives in a database tuned for per-user access.
What is the mailbox data model, and why denormalize read/unread state?
The mailbox is partitioned by user_id so that everything one person owns lives together and a read touches one partition. Within a user there’s a folders table, and the messages in a folder are stored in a wide-column table clustered by a time-ordered ID (a TIMEUUID), so “most recent first” is just reading the partition in order — no sort. The wrinkle is that a wide-column store like Cassandra can only efficiently filter on the columns that are part of the key; it cannot say “give me the unread ones in this folder” by scanning a non-key boolean. So you denormalize: keep a second table (or materialized view) that holds only unread messages, keyed the same way. Showing unread is now a direct read of that table, and marking a message read means deleting its row from the unread table. It’s two writes instead of one and two tables to keep consistent — the honest price of a data model that can’t filter on a plain column. This is the same denormalize-for-your-read-pattern move a news feed or a chat inbox makes.
How does an email actually get sent and received?
Two separate pipelines, both threaded through queues so nothing blocks in line. On send: an outgoing message is validated, written to an outgoing queue, and picked up by SMTP-sending workers that look up the recipient domain’s MX (mail exchanger) record in DNS and open an SMTP connection to that server. RFC 5321 frames the whole job plainly — “the objective of the Simple Mail Transfer Protocol (SMTP) is to transfer mail reliably and efficiently,” and a message “may pass through a number of intermediate relay or gateway hosts on its path from sender to ultimate recipient.” On receive: inbound SMTP connections hit a load balancer, land in an incoming queue, and processing workers run spam and virus checks, apply rules, write the message into the recipient’s mailbox store, update the cache, and push it to any connected client over a long-lived connection for real-time delivery. Decoupling both pipelines with queues is what lets a slow or unreachable recipient server be retried in the background instead of stalling the user who hit send.
What is IP warm-up and why does email deliverability depend on reputation?
Receiving providers decide whether to accept, junk, or block a message largely from the reputation of the sending IP address, and a brand-new IP has no history to trust. Amazon SES states it directly: “email providers are less likely to accept mail from new IP addresses that have little or no history,” and such mail “might end up in recipients’ junk mail folders, or might be blocked altogether.” The fix is to warm up: “gradually increase the amount of email that you send from that address before using it to its full capacity.” Push too hard too soon and you look like a spam cannon — “if an email provider sees a large, sudden increase in the number of emails that are sent from an IP address, they may block or throttle the delivery of messages from that address.” On top of IP reputation, three DNS-based authentication standards prove you are who you claim: SPF authorizes which servers may send for your domain, DKIM adds a cryptographic signature to each message, and DMARC ties them together — in Google’s words, it “tells receiving email servers what action to take on messages sent from your domain that don’t pass SPF or DKIM authentication.”
What happens when an email can’t be delivered?
It depends on why, and SMTP’s reply codes draw the line. A 4yz response is a transient failure — RFC 5321: “the error condition is temporary, and the action may be requested again” — a soft bounce, like the recipient server being briefly down or greylisting you. The sending worker keeps the message and retries on a backoff schedule (minutes, then hours, over a day or two) before finally giving up. A 5yz response is permanent — a hard bounce, like “no such mailbox” — and the SMTP client “SHOULD NOT repeat the exact request,” so you stop immediately and record the failure. Both outcomes, plus recipient complaints (someone hitting “mark as spam”), flow into feedback queues that update the sender’s reputation and prune bad addresses. Continuing to hammer an address that hard-bounced, or ignoring complaints, is exactly what wrecks the IP reputation the whole system depends on.

Related explainers