DDSA Solutions
Case Study6 min read·

Design an Email Service (Gmail)

How to design Gmail for interviews: SMTP ingest, mailbox storage, search indexing, spam filtering, push sync, and multi-device consistency.

Email looks boring until you design it. You are mixing a decades-old protocol (SMTP), a write-once-ish append log per mailbox, full-text search, spam ML, and near-real-time sync to phones. Interviewers like Gmail because it forces storage layout decisions and async pipelines without needing a flashy UI story.

Scope with the framework: send/receive for one provider, web + mobile clients, search, spam. Skip building a full anti-abuse PhD and skip inventing a new mail protocol — you speak SMTP/IMAP/HTTP APIs.

Functional requirements

  • Send mail to external domains; receive mail for @yourdomain users.
  • Inbox, sent, drafts, labels/folders; mark read/unread, archive, delete.
  • Full-text search across subject and body.
  • Attachments up to a size limit (e.g. 25 MB).
  • Push new mail to online clients; eventual sync when offline.
  • Optional: filters, vacation responder, threaded conversations (mention).

Non-functional

  • Durability: never lose accepted mail (at-least-once to durable store).
  • Send latency: user sees “Sent” in under a second; remote delivery can be async.
  • Search freshness: searchable within a few seconds of arrival.
  • Multi-device: last-write-wins on labels/read state is usually enough.
  • Prefer catching phishing over perfect ham precision — a missed phish in the inbox is worse than an occasional false positive in Spam.

Mailbox ≠ chat

Unlike chat, email is append-heavy, rarely edited, and must interoperate with the open internet. Your “source of truth” is the mailbox store plus an outbound queue — not a WebSocket fan-out graph.

Capacity sketch

Assume 500M users, average 20 messages/day received → ~100K msg/sec ingest globally (bursty around mornings). Average message 50 KB with headers → ~5 GB/s raw before compression and attachment offload. Attachments go to object storage; metadata and a truncated body preview stay in the mailbox DB. Search index size is often larger than the raw store — plan for that early.

High-level architecture

  1. SMTP edge (MX) — accept inbound TCP, TLS, greylisting basics.
  2. Ingest pipeline — parse MIME, virus scan, spam score, attach object IDs.
  3. Mailbox service — append message metadata + pointers; assign UID / history id.
  4. Object storage — attachment blobs (same pattern as file storage).
  5. Search indexer — async consumer writes to Elasticsearch / Vespa.
  6. Outbound MTA — queue for remote SMTP delivery with retries.
  7. Sync / push — notifications or long-poll / WebSocket for open clients.
  8. Web/API tier — REST/GraphQL for Gmail-like clients (API design).

Inbound path

  1. Remote MTA connects to your MX; you validate recipient exists and is under quota.
  2. Accept the DATA payload to a durable ingest queue (Kafka) — then 250 OK. Do not do heavy ML before accept if that risks timeouts; do cheap checks first.
  3. Workers parse MIME, store attachments, run spam/virus, compute thread_id.
  4. Mailbox append is transactional per user shard: insert row + bump history_id.
  5. Emit index and push events; never block the SMTP accept on search.

Idempotency matters: retries from remote MTAs can redeliver. Deduplicate on (Message-ID header, recipient) or content hash within a window so users do not see clones.

Data model

DataStoreNotes
User / quotaSQLStrong identity, billing tier
Message metadataSQL or wide-column shard by user_iduid, labels, flags, thread_id, pointers
Body / attachmentsObject storageImmutable blobs; CDN for large downloads
Search docsSearch clusterEventual; rebuildable from mailbox
Outbound queueKafka / SQSRetry with exponential backoff + DSN

Shard mailboxes by user_id (sharding). A single hot celebrity inbox is rare compared to chat celebrities, but corporate shared inboxes can spike — isolate them.

Send path

  1. Client posts draft → stored; “Send” creates an immutable outbound job and a Sent copy.
  2. Outbound workers resolve MX for each recipient domain, speak SMTP with TLS.
  3. On 4xx, retry; on hard 5xx, generate a bounce into the sender’s mailbox.
  4. Rate-limit per user and per destination domain (rate limiter) to protect reputation.

Search and spam

Index from, to, subject, body text, labels. Users expect Gmail operators (from:, has:attachment) — implement as structured fields, not only free text. Spam is a scored async stage: quarantine or foldering based on threshold. Keep a human-review / user “Report spam” feedback loop feeding the model offline. Do not pretend you will train transformers on the whiteboard.

Sync and consistency

Each mailbox exposes a monotonically increasing history_id. Clients sync deltas since last id — same spirit as Dropbox’s cursor. Read/unread flips are small metadata writes; conflicts across devices use last-write-wins on flag version. For CAP: choose CP for mailbox append (you accepted the mail), AP for search and badge counts.

Scaling and failure

  • Separate MX ingest from mailbox write tier so SMTP stays up if search is down.
  • Cache hot metadata pages of the inbox in Redis (caching).
  • Attachment virus scan timeouts → quarantine, never silent drop.
  • Outbound IP pools and warming — reputation is part of the design story.

Worked example

  1. Alice sends mail to bob@company.com.
  2. API writes Sent + outbound job; Bob’s MX accepts and enqueues ingest.
  3. Worker stores PDF in object storage, spam score low, appends to Bob’s shard.
  4. Indexer makes it searchable; Bob’s open web client gets a push with new history_id.
  5. Bob searches “invoice from:alice” — hits search cluster, hydrates bodies from mailbox pointers.

Interview narrative

Lead with MX → durable queue → mailbox append → async search/push. Call out attachment offload and per-user sharding. Contrast with notifications (you own both ends) and chat (ephemeral delivery receipts). That is a complete senior-level Gmail design without reinventing SMTP.

More in this series