Design an email service
Design a large email provider like Gmail or Outlook.com: hundreds of millions of mailboxes, mail arriving from the whole internet over a protocol from 1982, most of it spam, and users who expect search across years of mail to be instant on every device.
1. The prompt
"Design an email service: users send and receive email with anyone on the internet, organise it with labels or folders, search it, and use it on the web and on phones."
What makes it hard:
- You don't control the other side. Mail arrives over SMTP from millions of servers, many hostile; outgoing mail is judged by other providers' filters.
- Storage is enormous and per-user: years of mail and attachments for every account.
- Search and sync must feel instant on top of all that.
2. Frame the problem
How many users and messages?
About 500 million accounts; 20 billion incoming messages a day, most of them spam; 1 billion legitimate deliveries to inboxes; 300 million sent.
Spam dominates inbound volume: filtering must be cheap and happen before storage.
How much storage per user?
A quota of 15 GB; average use around 2 GB.
500 M × 2 GB ≈ 1 EB. Bodies and attachments belong in object storage, not a database.
Folders or labels?
Labels: one message can have several.
A message is stored once, with a set of label ids; "folders" are a view.
Conversation threading?
Yes, group replies into threads.
Thread ids are computed at delivery from headers (References, In-Reply-To) and subjects.
Which clients?
Web, mobile apps, and third-party clients over IMAP.
We need a sync protocol with change tracking, plus push for new mail.
Search?
Full text over all of a user's mail, results in under a second.
An inverted index per user (or per partition of users), updated at delivery.
Security expectations?
Encryption in transit and at rest; protection against phishing and spoofing; account takeover defences.
Sender authentication (SPF, DKIM, DMARC) is part of the inbound pipeline.
3. What it must do
- A user receives email from anyone on the internet, filtered for spam and malware, in their inbox within seconds.
- A user sends email to anyone, and it's delivered (or they learn why not).
- A user reads, labels and searches their mail on any device, kept in sync.
Not now: calendar and contacts, end-to-end encryption, mailing-list hosting, ads.
4. How well it must do it
| Target | Why this number |
|---|---|
| Never lose an accepted message: once we answer SMTP "250 OK", it's durable data safety | The sender deletes its copy when we accept; losing it then loses it for good. |
| Delivery to the inbox within 10 s at p99 after acceptance speed | People wait for verification codes. |
| Spam and phishing kept out of the inbox (well over 99%), with very few legitimate messages misfiled security | A missed bank email is worse than one spam message. |
| Search under 500 ms p99 over years of mail speed | Search replaced folders for most users. |
| Availability 99.9%+ for reading; inbound mail queues briefly rather than bounces surviving failures | SMTP senders retry for days, so short inbound outages delay mail but don't lose it. |
5. Numbers that change the design
| Quantity | Working | → so we… |
|---|---|---|
| Inbound connections | 20 B messages a day ≈ 230,000/s average, peaks ~3× | → so cheap rejection (IP reputation, rate limits) happens at the edge, before the message body is even accepted |
| Storage | 500 M users × 2 GB ≈ 1 EB; bodies average ~75 KB, attachments far larger | → so bodies and attachments go to object storage (D35), compressed; metadata and indexes stay in databases |
| Duplicate attachments | The same PDF sent to 50 colleagues | → so attachments are stored content-addressed (by hash) and deduplicated |
| Per-user data | A heavy user has 500,000 messages | Everything is naturally partitioned by user → so we shard by user id; one user's mailbox lives in one partition |
Illustrative numbers. Skipped: send volume (small compared with inbound).
6. The nouns
- Mailbox (one per user) with labels.
- Message: immutable content (headers, body, attachments) plus mutable state (labels, read, starred).
- Thread: messages grouped as a conversation.
- Change log: every change to a mailbox, numbered, for sync.
message (user_id, message_id) → thread_id, labels[], flags, received_at, size, blob_ref, snippet blob sha256 → compressed body or attachment bytes (object storage, shared across users) thread (user_id, thread_id) → subject, message_ids[], last_activity change_log (user_id, modseq) → kind (added | labels_changed | deleted), message_id search_index per user partition: term → postings of message_ids
7. The contract
Internet-facing
SMTP (port 25): receive from other providers; STARTTLS, MTA-STS for encryption policy
SMTP submission (port 587): our users' clients send through us, authenticated
IMAP: third-party clients
Our apps (JMAP-style JSON API)
GET /v1/mailboxes/me/messages?label=INBOX&page_token=… → summaries
GET /v1/messages/{id} → full message
POST /v1/messages/{id}/labels {add[], remove[]}
GET /v1/search?q=invoice from:acme after:2025-01-01
GET /v1/changes?since_modseq=8841 → changes, new modseq
POST /v1/messages:send {to[], subject, body, attachments[]} Idempotency-Key: …
Push: a WebSocket or mobile push "mailbox changed" signal; clients then fetch changes
The user comes from the session token. Sync is "give me everything since change number N", which works for a phone that was offline for a minute or a month.
Data flow (inbound): SMTP edge (reputation, rate limits) → accept into a durable queue → authenticate sender (SPF, DKIM, DMARC) → spam and malware scoring → store blobs → write message metadata and change log → index for search → push to devices.
8. First working version
SMTP servers, a durable delivery queue, a filtering stage, mailbox storage sharded by user, a search indexer, and an API with sync and push.
8.1 Receiving a message
The SMTP server says "250 OK" only after the message is written to a replicated queue: that's the durability promise. Everything after (filtering, storage, indexing) runs from the queue and can retry.
8.2 Sending a message
Outgoing mail is signed with DKIM, put on an outbound queue per destination domain, and delivered over SMTP to the recipient's provider (found through its MX record). Temporary failures are retried with backoff for up to a few days; permanent ones produce a bounce message to the sender.
8.3 Reading and syncing
A client lists the inbox from message metadata (labels, snippet, date) and fetches bodies from object storage only when opened. Every change bumps the mailbox's change number; devices ask for changes since their last number, so a label applied on the laptop shows up on the phone.
9. Make it hold up
9.1 Filtering 20 billion messages a day security · cost
| Approach | Why | |
|---|---|---|
| ✗ Bad | Accept everything, store it, then run one big classifier | We'd store and process billions of spam messages a day. |
| ~ Good | A classifier before storage | Saves storage, but every spam connection still costs a full SMTP conversation and body transfer. |
| ✓ Great | A funnel: reject by IP reputation and rate limits at connection time; check sender authentication; then content scoring (machine learning, URL and attachment scanning); finally per-user signals ("report spam", contacts) | Each stage is cheaper than the next and removes most of what reaches it. User feedback trains the models. |
The threshold between "inbox" and "spam folder" trades missed spam against misfiled real mail; that trade-off is tuned per message category, and messages that fail authentication for a domain that publishes a strict policy are treated much more harshly.
9.2 Who really sent this? security
- SPF: the sender's domain publishes which IP addresses may send its mail; we check the connecting IP against the envelope sender's domain.
- DKIM: the sending server signs headers and body; we verify the signature with a public key in the signing domain's DNS.
- DMARC: passes if SPF or DKIM passes for a domain aligned with the visible From address; the From domain's policy says what to do otherwise (none, quarantine, reject), and where to send reports.
- ARC preserves authentication results across forwarders and mailing lists that would otherwise break them.
Try the scenarios in the lab in section 13: forwarding breaks SPF but usually not DKIM; a mailing list that edits the subject breaks DKIM.
9.3 An exabyte of mail cost · data safety
| Approach | Why | |
|---|---|---|
| ✗ Bad | Messages as rows in a relational database | Exabytes of large text and binary in a database is slow and very expensive. |
| ~ Good | A file per user mailbox (mbox/maildir) on file servers | Simple, but hard to replicate, search or share across users. |
| ✓ Great | Split metadata from content: metadata and labels in a database sharded by user; bodies and attachments compressed in object storage, content-addressed so identical attachments are stored once; older mail moved to colder tiers | Each part on the storage that suits it; deduplication and tiering cut cost; durability from the object store's replication or erasure coding. |
Deduplicated blobs need reference counting (or periodic mark-and-sweep) so a shared attachment is deleted only when no mailbox references it.
9.4 Search over years of mail search
Build an inverted index per user (or per small group of users in one index shard), updated asynchronously at delivery from the queue. Queries touch only one user's index, so they're small even when the total corpus is huge. Structured operators (from:, has:attachment, label:, dates) are fields in the same index. Recent mail is in a small, fast-updating segment; older segments are merged in the background, as in any search engine (Module 12).
9.5 Getting our mail delivered delivery
- Reputation: other providers judge our sending IPs and domains. Separate pools for different kinds of mail, warm up new IPs slowly, and stop spammers using our accounts (outbound spam scoring, send limits for new accounts).
- Authenticate everything we send: SPF, DKIM signatures, a DMARC policy.
- Queue per destination domain with its own rate limits and backoff, so one slow provider doesn't hold up the rest.
- Encrypt in transit with STARTTLS, and honour recipients' MTA-STS policies that require it.
9.6 Sync and push for hundreds of millions of devices delivery speed
Devices don't poll every few seconds. Phones get a platform push notification; web clients hold a connection (WebSocket or long poll) to a gateway that subscribes to their user's change events. Either way, the signal only says "something changed", and the client fetches the delta from the change log. Change logs are trimmed after some weeks; a device that's further behind does a full resync.
9.7 The whole design after the deep dives
10. Interviewer follow-ups
A user receives 50,000 emails an hour (a misconfigured script).
Per-recipient rate limits at the edge return temporary failures (4xx), so senders retry later instead of filling the mailbox; quotas cap storage. The partition holding that user is protected from one hot mailbox.
Our inbound edge is down for an hour.
Sending servers retry for days, so mail is delayed, not lost. MX records list several sites so senders try another first.
How do you delete an account's data?
Delete metadata, indexes and change logs; decrement references on blobs and delete unreferenced ones; purge from backups on their retention schedule. Per-user encryption keys allow crypto-shredding.
Undo send.
Hold outgoing messages in the outbound queue for a short delay (say 10 seconds) before delivery; undo deletes it from the queue. Once handed to another provider, it can't be recalled.
A phishing campaign uses lookalike domains.
Authentication can't stop a domain that is genuinely someone else's. Content models, URL reputation, new-domain signals and warnings ("first message from this sender") do; user reports feed back quickly.
Why is the change log per user?
Sync, push and conflict handling are all per mailbox; a per-user sequence is cheap, ordered and easy to shard with the mailbox.
Ten times the users.
Mailbox shards, index shards and object storage grow horizontally; edge and filtering scale with traffic. The per-user design means no global bottleneck.
11. When things break
| Failure | What users see | How we detect it | What limits the damage |
|---|---|---|---|
| Inbound site lost | Delayed mail | SMTP health per site | Multiple MX sites; senders retry |
| Filtering stage slow | Mail arrives late | Queue depth and age | Durable queue absorbs it; scale filters |
| Mailbox shard down | Some users can't read mail | Error rate per shard | Replicated shards fail over; inbound queues until it's back |
| Search index behind | New mail missing from search | Index lag | Recent mail searched from metadata as a fallback |
| Our IPs blocklisted | Sent mail bounces at some providers | Bounce rates per destination | Separate IP pools, outbound spam controls, delisting process |
| Spam wave | Some spam in inboxes | User spam reports spike | Fast model updates from reports; temporary rules |
12. Wrap-up and the clock
Trade-offs we made:
- Acknowledge SMTP only after a durable queue write; process asynchronously.
- A filtering funnel from cheap to expensive.
- Metadata in sharded databases, content in object storage, deduplicated.
- Everything partitioned by user: mailbox, change log, search index.
- Push a signal, sync the delta.
What we'd watch: inbound queue age · time from acceptance to inbox (p99) · spam reports and "not spam" rescues · search latency and index lag · outbound bounce and deferral rates per destination · storage per user and dedup ratio.
At 10×: more shards everywhere; the per-user partitioning keeps it linear. Spam volume grows faster than users, so the funnel's early stages matter most.
A 60-minute interview
| 0–5 | Frame: users, inbound volume, storage, clients |
| 5–10 | Must do, how well, numbers |
| 10–15 | Nouns, protocols, inbound data flow |
| 15–25 | First version: receive, send, read and sync |
| 25–50 | Deep dives: filtering (9.1), authentication (9.2), storage (9.3) |
| 50–60 | Search or sync, follow-ups, wrap-up |
A 90-minute interview
| 0–8 | Frame, including labels and threading |
| 8–15 | Must do, how well, numbers |
| 15–22 | Nouns, protocols, data flow |
| 22–35 | First working version |
| 35–75 | Deep dives 9.1–9.6 and the final picture |
| 75–85 | Follow-ups: floods, undo send, deletion |
| 85–90 | Trade-offs, metrics, 10× |
| Level | What a strong answer shows |
|---|---|
| Mid-level | SMTP in and out, a queue, mailbox storage per user, labels, an API for clients. |
| Senior | Durable acceptance, a filtering funnel, SPF/DKIM/DMARC, metadata vs blob storage with dedup, per-user search indexes, change-log sync with push. |
| Staff+ | Sender reputation as a system to protect, the spam threshold as a product trade-off, storage tiering economics, account deletion across deduplicated blobs and backups, abuse from our own users. |
Common mistakes:
- Ignoring that most inbound mail is spam.
- Saying "250 OK" before the message is durable.
- Storing message bodies in the main database.
- One global search index queried for every user.
- Clients polling for new mail.
- Forgetting outbound reputation.
13. Try it, check yourself, read more
Who sent this? An authentication lab. Pick a delivery scenario and see how SPF, DKIM and DMARC judge it. The visible From address is billing@bank.example, and bank.example publishes p=reject.
Simplified: real receivers also use ARC, reputation and content signals. "Aligned" means the authenticated domain matches the From domain (relaxed alignment allows subdomains).
When may the SMTP server reply 250 OK to a sender?
When does DMARC pass?
Where should attachments be stored?
How do devices stay in sync without polling?
| Resource | Why |
|---|---|
| RFC 5321: SMTP | How mail moves between servers, including retries and bounces. |
| SPF (RFC 7208), DKIM (RFC 6376), DMARC (RFC 7489), ARC (RFC 8617) | Sender authentication, from the standards. |
| MTA-STS (RFC 8461) | Requiring encryption between mail servers. |
| JMAP (RFC 8620) and JMAP for Mail (RFC 8621) | A modern JSON sync protocol with change tracking. |
| Email sender guidelines (Google) | What a large receiver requires of senders today. |