Design an Email Service like Gmail
Problem
Design a web email service like Gmail. Users send and receive email with any other email provider on the internet, not only within our service. Users read their mail in a web and mobile client that organizes messages into threads and labels, and can search their entire mailbox in about a second. Spam and malware must be filtered before they reach the inbox. Attachments up to 25 MB are supported. The service has 1.5 billion users, 500 million daily active users, and receives about 100 billion inbound messages per day, more than half of which are spam.
- Do we need IMAP and POP access for third-party clients, or only our own apps?
- How much storage does each user get, and what happens when it is full?
- How long is deleted mail kept in trash before it is purged?
- Are features like scheduled send, undo send, and auto-categorization (promotions, social) in scope?
- What compliance requirements apply, such as legal hold or retention policies for business accounts?
- Should search cover attachment contents?
Items in bold are existing requirements. The rest are suggestions. Confirm with the interviewer whether they are relevant first.
- Send email to, and receive email from, any internet email address over SMTP.
- Show mail in threaded conversations with labels (Inbox, Sent, custom labels).
- Search the full mailbox by sender, subject, body, and date.
- Filter spam, phishing, and malware before delivery to the inbox.
- Support attachments up to 25 MB.
- Push new mail to open clients and mobile devices.
- Undo send within a short window.
Items in bold are existing requirements. The rest are suggestions. Confirm with the interviewer whether they are relevant first.
- 100 billion inbound messages per day is about 1.2 million per second average and 3 million at peak, most rejected or marked as spam.
- About 40 billion messages per day are actually stored: at 75 KB average including attachments, that is 3 PB per day before deduplication and compression.
- Total storage across 1.5 billion users at an average of 5 GB each is about 7.5 EB logical.
- Durability: an accepted message is never lost, so we only return SMTP 250 after the message is durably stored.
- Inbox list loads in under 200 ms and search returns in under 1 second at p99.
- Spam false positives (good mail in spam) must be extremely rare; a few false negatives are tolerable.
Inbound SMTP servers accept connections from other mail servers, run cheap reputation checks to reject obvious abuse, durably queue accepted messages, and pass them through a filtering pipeline that classifies spam and scans attachments. A delivery service writes each message to the recipient's mailbox store, which is partitioned by user and stores message metadata, labels, and threads separately from bodies and attachments in blob storage, then updates the user's search index and notifies open clients. Outbound mail flows through a send queue to SMTP relays that sign messages and retry with backoff.
Loading diagram...
Receive mail. A remote server looks up our MX records and opens an SMTP connection. The inbound server checks the sending IP's reputation, verifies SPF, DKIM, and DMARC, and rejects unknown recipients during the SMTP conversation. For accepted messages it writes the raw message to a replicated inbound queue and only then replies 250 OK.
Filter and deliver. The filter scores each message using sender reputation, content features, links, and user feedback, and scans attachments for malware. The delivery service then stores the body and attachments in blob storage (keyed by content hash), writes a metadata row to the recipient's mailbox with labels such as INBOX or SPAM, assigns it to a thread, and sends an event to the search indexer and the notification service.
Read mail. The client calls GET /threads?label=INBOX&pageToken=. The mail API reads thread summaries from the mailbox metadata store, newest first, and returns them. Opening a thread fetches the message bodies from blob storage, with attachments downloaded only on click.
Send mail. The client posts the draft. The API stores it in the Sent label, writes it to the outbound queue with a short hold for undo send, and returns. Relay workers look up the recipient domain's MX records, sign the message with DKIM, and deliver over TLS, retrying with exponential backoff for up to several days and returning a bounce message if delivery fails.
Search. The client sends GET /search?q=from:alice invoice. The API queries the user's own search index, which returns message IDs, and hydrates them from the metadata store.
Accepts mail from the internet and decides what reaches the inbox.
- Custom SMTP servers on many IPs, behind DNS MX records with several priorities.
- A replicated queue (Kafka or a log-structured spool on disk) for accepted messages.
- ML spam classifiers plus rule-based checks, with sandboxed attachment scanning.
Diagram
Loading diagram...
Scalability
Most abusive traffic is rejected cheaply at the connection stage based on IP reputation, before any content is read, which is what keeps billions of spam attempts affordable. Filtering is stateless and scales horizontally by consuming queue partitions.
Tradeoffs
Queueing before filtering means a slow classifier never causes the server to time out remote senders, and a crash after 250 OK never loses mail. Rejecting during the SMTP session (instead of accepting and silently dropping) tells legitimate senders their mail failed, which is required for correct email behavior.
Per-user store of messages, threads, and labels that powers inbox listing.
- A wide-column store such as Bigtable or Cassandra, partitioned by user ID.
Data Model
messages:user_id(partition key),message_id,thread_id,from,to,subject,snippet,received_at,labels,blob_key,size.label_index:user_idandlabel(partition key),received_atdesc,thread_id, for paginating a label view.threads:user_id,thread_id,last_message_at,message_ids,participants.
Scalability
Every request is scoped to one user, so partitioning by user ID gives near-perfect locality. Threading uses the References and In-Reply-To headers plus normalized subject. The label index is denormalized so listing a label is one sorted range scan.
Tradeoffs
Labels instead of folders let a message appear in several views with one stored copy. Storing metadata apart from bodies keeps the hot inbox list small and fast, since most opened views only need sender, subject, and snippet.
Stores raw message bodies and attachments.
- Object storage with erasure coding and tiering (hot for recent mail, cold for older mail).
Scalability
Attachments are content addressed, so a newsletter image or a file sent to 1,000 employees is stored once and referenced many times. Bodies are compressed. Mail older than a few months moves to a cold tier, since most reads are of recent mail.
Tradeoffs
Deduplicating by hash requires reference counting before deletion, which adds complexity, but at exabyte scale the savings dominate.
Maintains a full-text index of each user's mailbox.
- A custom inverted index per user, stored in sharded index servers, or Elasticsearch with routing by user ID for smaller deployments.
Scalability
Because users only search their own mail, each user's index is small (a few hundred MB at most) and a query touches exactly one shard. New messages are indexed within seconds from the delivery event stream.
Tradeoffs
A single global index would make every query scan data from millions of users and complicate privacy. Per-user indexes waste some space on duplicated terms but make queries fast and isolation simple.
Sends mail to external domains reliably and with good reputation.
- SMTP relay workers with DKIM signing, a DNS cache for MX lookups, and a retry queue.
API
POST /messages/sendwith{ to, cc, bcc, subject, body, attachmentIds }returns{ messageId }.POST /messages/:id/undocancels a send during the hold window.
Tradeoffs
Rate limits per recipient domain prevent overwhelming smaller providers, and outbound abuse checks protect the shared IP reputation that all users depend on. A short hold before relaying makes undo send possible without recalling mail that already left.