Case Study: Design a Distributed Email Service
“Email nhìn đơn giản — gửi thư từ A đến B. Nhưng khi 1 tỷ người gửi cùng lúc, 80% là spam, và mỗi email phải tìm được trong milliseconds — đó mới là engineering thật sự.”
Tags: system-design email case-study alex-xu-vol2 distributed-system Student: Hieu Reference: Alex Xu, System Design Interview Volume 2 — Chapter 5: Design a Distributed Email Service Prerequisite: Tuan-08-Message-Queue · Tuan-15-Data-Security-Encryption · Tuan-14-AuthN-AuthZ-Security Liên quan: Tuan-19-Design-Notification-System · Tuan-17-Design-Chat-System · Tuan-13-Monitoring-Observability
Context & Why — Tại sao Email Service quan trọng?
Analogy: Hệ thống Bưu điện Quốc gia
Hieu, hãy tưởng tượng hệ thống email như bưu điện quốc gia với quy mô toàn cầu:
| Bưu điện | Email System | Giải thích |
|---|---|---|
| Người gửi thư | Sender (SMTP client) | Soạn thư, dán tem, bỏ vào thùng thư |
| Bưu cục gửi | Outgoing SMTP Server | Nhận thư từ người gửi, kiểm tra hợp lệ, đóng dấu |
| Xe tải vận chuyển | SMTP relay / Internet | Chuyển thư qua mạng lưới giữa các bưu cục |
| Bưu cục nhận | Incoming SMTP Server | Nhận thư đến, kiểm tra địa chỉ, phân loại |
| Bộ phận kiểm duyệt | Spam Filter + Virus Scanner | Lọc thư rác, kiểm tra chất cấm (malware) |
| Phòng phân loại | Email Processing Pipeline | Phân loại theo người nhận, folder, label |
| Tủ thư cá nhân | Mailbox Storage | Lưu trữ thư cho từng người, sẵn sàng để đọc |
| Quầy tìm thư | Search Service (Elasticsearch) | Tìm kiếm thư theo nội dung, người gửi, ngày tháng |
| Người nhận đến lấy thư | Email Client (IMAP/POP3/API) | Đọc thư, trả lời, xóa, di chuyển |
Tại sao đây là bài toán khó?
Email nghe có vẻ “cũ” nhưng thực tế là một trong những distributed system phức tạp nhất:
- Scale khổng lồ: Gmail có 1.8 tỷ users, xử lý hàng trăm tỷ emails/ngày
- Spam chiếm 80%+ traffic: Phải filter trước khi đến user, nếu không mailbox sẽ ngập
- Search phải nhanh: User kỳ vọng tìm email trong milliseconds, dù có hàng triệu emails
- Reliability tuyệt đối: Mất email = mất hợp đồng, mất tiền, mất niềm tin
- Async by nature: Email không phải real-time — sender gửi xong không biết khi nào receiver đọc
- Attachment storage: File đính kèm chiếm phần lớn chi phí lưu trữ
- Security: Email là vector tấn công số 1 (phishing, malware, social engineering)
- Protocol legacy: SMTP ra đời từ 1982 (RFC 821), phải backward compatible
Hieu nhớ: Email system là write-heavy (nhận emails liên tục), read-heavy (user đọc nhiều lần), và search-heavy (tìm kiếm mọi lúc). Rất ít hệ thống phải tối ưu cả 3 workload cùng lúc.
Step 1 — Understand the Problem & Establish Design Scope
1.1 Clarifying Questions (Câu hỏi làm rõ)
Trong interview, luôn hỏi trước khi thiết kế. Dưới đây là các câu hỏi quan trọng:
| Câu hỏi | Câu trả lời giả định | Tại sao hỏi |
|---|---|---|
| Scale bao lớn? | 1 tỷ users (Gmail-like) | Quyết định toàn bộ storage + compute strategy |
| Hỗ trợ gửi và nhận email? | Cả hai (send + receive) | Hai flow rất khác nhau |
| Cần email search không? | Có — full-text search | Search engine riêng (Elasticsearch) |
| Spam filter? | Có — ML-based | Spam là 80%+ traffic, không filter = chết |
| Attachment support? | Có — tối đa 25MB/file | Blob storage, virus scanning |
| Web client hay mobile? | Cả hai — web + mobile + desktop | API-first design |
| IMAP/POP3 support? | IMAP (modern), POP3 (legacy) + REST API | Multi-protocol support |
| Threading/conversation? | Có — Gmail-style conversation view | Message-ID + In-Reply-To headers |
| Encryption? | TLS in transit, encryption at rest | Compliance requirement |
| Read receipts? | Optional feature | Tracking pixel hoặc header |
| Drafts & scheduled send? | Có | Storage + scheduler service |
1.2 Functional Requirements (Yêu cầu chức năng)
- FR1: Gửi email (send) — compose, add recipients (To/CC/BCC), attach files, send
- FR2: Nhận email (receive) — accept incoming email từ bất kỳ SMTP server nào trên Internet
- FR3: Đọc email (read) — inbox, sent, drafts, trash, custom folders/labels
- FR4: Tìm kiếm email (search) — full-text search theo subject, body, sender, date range, attachments
- FR5: Spam/phishing filter — tự động phân loại spam, phishing, malware
- FR6: Attachment handling — upload, download, preview (images, PDFs)
- FR7: Threading/conversation — group related emails thành conversation
- FR8: Labels & folders — organize emails với labels (Gmail) hoặc folders (Outlook)
- FR9: Sync across devices — đọc trên phone, đánh dấu read hiện trên laptop
- FR10: Drafts & scheduled send — lưu nháp, hẹn giờ gửi
- FR11: Auto-reply & vacation responder — trả lời tự động khi vắng mặt
1.3 Non-functional Requirements (Yêu cầu phi chức năng)
| Requirement | Target | Ghi chú |
|---|---|---|
| Scalability | 1B users, 40B emails/day received | Bao gồm cả spam |
| Availability | 99.99% (four 9s) | < 52 phút downtime/năm |
| Latency (send) | Email gửi đi trong < 5 giây | Từ lúc user nhấn Send đến lúc vào outgoing queue |
| Latency (search) | Search results < 500ms | Full-text search trên hàng triệu emails per user |
| Latency (read) | Inbox load < 200ms | Inbox là trang được truy cập nhiều nhất |
| Durability | 99.999999999% (eleven 9s) | Không bao giờ mất email |
| Storage efficiency | Deduplication for attachments | Cùng 1 file gửi cho 100 người = lưu 1 lần |
| Compliance | CAN-SPAM, GDPR, HIPAA (healthcare) | Legal requirements per region |
| Security | TLS in transit, encryption at rest | SPF/DKIM/DMARC cho authentication |
Step 2 — High-Level Design
2.1 Email Protocols Overview
Trước khi vẽ architecture, Hieu cần hiểu rõ các protocol:
SMTP — Simple Mail Transfer Protocol (Gửi email)
| Thuộc tính | Chi tiết |
|---|---|
| Port | 25 (server-to-server), 587 (client submission with STARTTLS), 465 (implicit TLS) |
| RFC | RFC 5321 (SMTP), RFC 3207 (STARTTLS) |
| Vai trò | Chuyển email từ sender → sender’s server → receiver’s server |
| Đặc điểm | Text-based protocol, stateless per transaction, store-and-forward |
| Hạn chế | Không có authentication mặc định (vì ra đời 1982), push-only (không pull) |
SMTP flow cơ bản:
- Client kết nối TCP đến SMTP server (port 587)
EHLO— chào hỏi, negotiate extensionsSTARTTLS— upgrade lên TLS encrypted connectionAUTH— authenticate user (LOGIN, PLAIN, hoặc OAuth2)MAIL FROM— khai báo sender addressRCPT TO— khai báo recipient address(es)DATA— gửi nội dung email (headers + body + MIME attachments)- Server trả
250 OK— email đã được accepted vào queue
IMAP — Internet Message Access Protocol (Đọc email — modern)
| Thuộc tính | Chi tiết |
|---|---|
| Port | 993 (TLS), 143 (STARTTLS) |
| RFC | RFC 9051 (IMAP4rev2) |
| Vai trò | Client đọc, quản lý email trên server |
| Đặc điểm | Server-side storage — email ở trên server, client sync |
| Ưu điểm | Multi-device sync, folder management, partial fetch |
| Nhược điểm | Phức tạp hơn POP3, server phải lưu trữ tất cả |
POP3 — Post Office Protocol v3 (Đọc email — legacy)
| Thuộc tính | Chi tiết |
|---|---|
| Port | 995 (TLS), 110 (STARTTLS) |
| RFC | RFC 1939 |
| Vai trò | Download email từ server về client |
| Đặc điểm | Client-side storage — download rồi xóa trên server (mặc định) |
| Ưu điểm | Đơn giản, ít tải server |
| Nhược điểm | Không sync across devices, một khi download là mất trên server |
Modern API Approach (Gmail API, Microsoft Graph)
| Thuộc tính | Chi tiết |
|---|---|
| Protocol | HTTPS REST / GraphQL |
| Auth | OAuth 2.0 |
| Vai trò | Thay thế IMAP/POP3 cho web/mobile clients |
| Ưu điểm | Rich features (labels, threads, search), push notifications, better mobile support |
| Nhược điểm | Vendor-specific, không standard across providers |
Hieu nhớ: Hệ thống email hiện đại thường dùng SMTP cho gửi (vì đó là Internet standard), nhưng REST API cho đọc (vì flexible hơn IMAP). IMAP vẫn cần hỗ trợ cho backward compatibility với các email clients cũ (Thunderbird, Outlook desktop).
So sánh IMAP vs POP3 vs API
| Tiêu chí | POP3 | IMAP | REST API |
|---|---|---|---|
| Email lưu ở đâu? | Client | Server | Server |
| Multi-device sync | Không | Có | Có |
| Offline support | Tốt (đã download) | Partial (cache) | Cần implement |
| Bandwidth | Tải hết 1 lần | Tải theo nhu cầu | Tải theo nhu cầu |
| Search | Client-side | Server-side | Server-side |
| Push notification | Không | IMAP IDLE | Webhooks / FCM |
| Complexity | Đơn giản | Trung bình | Cao (nhưng flexible) |
2.2 High-Level Architecture
flowchart TB subgraph "Email Clients" WEB["Web Client<br/>(React/Angular)"] MOB["Mobile Client<br/>(iOS/Android)"] DESK["Desktop Client<br/>(Thunderbird/Outlook)"] EXT["External SMTP<br/>Servers"] end subgraph "API & Protocol Layer" LB["Load Balancer<br/>(L7 — Nginx/Envoy)"] API["Email API Service<br/>(REST/GraphQL)"] SMTP_IN["SMTP Inbound Server<br/>(Receiving — Port 25)"] SMTP_OUT["SMTP Outbound Server<br/>(Sending — Port 587)"] IMAP_SRV["IMAP Server<br/>(Port 993)"] end subgraph "Processing Pipeline" direction TB SPAM["Spam Filter<br/>(ML-based)"] VIRUS["Virus Scanner<br/>(ClamAV)"] ROUTE["Email Router<br/>(Routing Rules)"] THREAD["Threading Service<br/>(Conversation Grouping)"] INDEX["Search Indexer<br/>(Async)"] end subgraph "Message Queue Layer (Kafka)" Q_OUT["Outgoing Queue"] Q_IN["Incoming Queue"] Q_INDEX["Index Queue"] Q_NOTIFY["Notification Queue"] end subgraph "Storage Layer" META[("Metadata DB<br/>(Cassandra/Bigtable)<br/>Subject, From, To, Date,<br/>Labels, Read/Unread")] BODY[("Email Body Store<br/>(Distributed Blob)<br/>HTML/Text content")] ATTACH[("Attachment Store<br/>(S3/GCS)<br/>Files, Images")] SEARCH[("Search Index<br/>(Elasticsearch/Lucene)<br/>Full-text search")] CACHE[("Cache Layer<br/>(Redis/Memcached)<br/>Inbox, recent emails")] end subgraph "Supporting Services" AUTH["Auth Service<br/>(OAuth2, SPF/DKIM)"] NOTIF["Push Notification<br/>(FCM/APNs)"] ANALYTICS["Analytics<br/>(Delivery, Bounce rates)"] end WEB & MOB --> LB LB --> API DESK --> IMAP_SRV DESK --> SMTP_OUT EXT --> SMTP_IN API --> Q_OUT SMTP_OUT --> Q_OUT SMTP_IN --> Q_IN Q_IN --> SPAM SPAM --> VIRUS VIRUS --> ROUTE ROUTE --> THREAD THREAD --> META THREAD --> BODY THREAD --> Q_INDEX THREAD --> Q_NOTIFY Q_OUT --> SMTP_OUT Q_INDEX --> INDEX INDEX --> SEARCH Q_NOTIFY --> NOTIF API --> META API --> BODY API --> ATTACH API --> SEARCH API --> CACHE IMAP_SRV --> META IMAP_SRV --> BODY API --> AUTH style SPAM fill:#f44336,stroke:#333,stroke-width:2px,color:#fff style SEARCH fill:#2196F3,stroke:#333,stroke-width:2px,color:#fff style META fill:#4CAF50,stroke:#333,stroke-width:2px,color:#fff style Q_IN fill:#FF9800,stroke:#333,stroke-width:2px,color:#fff style Q_OUT fill:#FF9800,stroke:#333,stroke-width:2px,color:#fff
2.3 Ba luồng chính (Three Main Flows)
Email system có 3 luồng riêng biệt — mỗi luồng có architecture khác nhau:
| Flow | Protocol | Hướng | Mô tả |
|---|---|---|---|
| Sending Flow | SMTP (outbound) | User → Internet | User soạn email, gửi ra ngoài |
| Receiving Flow | SMTP (inbound) | Internet → User | Email từ bên ngoài đến mailbox |
| Reading Flow | IMAP / REST API | Server ↔ Client | User đọc, search, quản lý email |
Step 3 — Deep Dive
3.1 Email Sending Flow (Luồng gửi email)
sequenceDiagram participant User as User (Client) participant API as Email API participant Auth as Auth Service participant Queue as Outgoing Queue<br/>(Kafka) participant SMTP as SMTP Outbound<br/>Server participant DNS as DNS Server participant Remote as Receiver's SMTP<br/>Server participant RMailbox as Receiver's<br/>Mailbox User->>API: POST /emails/send<br/>{to, subject, body, attachments} API->>Auth: Validate token + rate limit Auth-->>API: OK Note over API: Validate recipients,<br/>check attachment size,<br/>render HTML template API->>Queue: Enqueue outgoing email API-->>User: 202 Accepted<br/>(async processing) Queue->>SMTP: Dequeue email Note over SMTP: Sign email with<br/>DKIM private key SMTP->>DNS: MX record lookup<br/>for recipient domain DNS-->>SMTP: MX: mail.receiver.com<br/>Priority: 10 SMTP->>Remote: EHLO sender.com Remote-->>SMTP: 250-STARTTLS SMTP->>Remote: STARTTLS Note over SMTP,Remote: TLS Handshake SMTP->>Remote: MAIL FROM:<user@sender.com> Remote-->>SMTP: 250 OK SMTP->>Remote: RCPT TO:<friend@receiver.com> Remote-->>SMTP: 250 OK SMTP->>Remote: DATA + email content Remote-->>SMTP: 250 OK — Message queued Note over Remote: Receiver's pipeline:<br/>SPF check → DKIM verify →<br/>Spam filter → Virus scan →<br/>Route to mailbox Remote->>RMailbox: Store in recipient's mailbox
Sending Flow — Chi tiết từng bước
Bước 1: User composes email
- Client (web/mobile) gọi
POST /api/v1/emails/sendvới payload: recipients, subject, body (HTML + plain text), attachments - Nếu có attachments: client upload trước lên blob storage, nhận lại attachment IDs → đính kèm vào email request
Bước 2: API Service validates
- Authentication: verify OAuth2 token
- Rate limiting: chống abuse (ví dụ: max 500 emails/day per user, max 100 recipients/email)
- Validation: check email format, attachment size (max 25MB total), recipient count
- Save to Sent folder: lưu bản copy vào sender’s mailbox metadata + body store
Bước 3: Enqueue to Outgoing Queue
- Email được đẩy vào Kafka outgoing queue
- Trả về
202 Acceptedcho user ngay (async processing) - Tại sao async? Vì SMTP delivery có thể mất vài giây đến vài phút (DNS lookup, TLS handshake, remote server slow)
Bước 4: SMTP Outbound Server processes
- Dequeue email từ Kafka
- DKIM signing: ký email bằng private key của domain → receiver có thể verify
- MX record lookup: query DNS để tìm receiver’s SMTP server
- Ví dụ: gửi đến
friend@gmail.com→ DNS trả vềalt1.gmail-smtp-in.l.google.com(priority 5) - Nếu primary MX server down → fallback sang server có priority cao hơn (số lớn hơn)
- Ví dụ: gửi đến
- TLS negotiation: STARTTLS để encrypt connection
- SMTP handshake: EHLO → MAIL FROM → RCPT TO → DATA
Bước 5: Error handling & retry
- Nếu remote server trả 4xx (temporary failure): retry với exponential backoff (1min, 5min, 30min, 2h, 8h — lên đến 72 giờ)
- Nếu remote server trả 5xx (permanent failure): generate bounce email gửi lại cho sender
- Nếu DNS lookup fail: retry
- Nếu TLS handshake fail: thử gửi không TLS (nếu policy cho phép) hoặc fail
Hieu nhớ: Email delivery là best-effort — không có guarantee rằng email sẽ đến. Sender chỉ biết email đã được accepted bởi receiver’s server (250 OK), không biết nó có đến inbox hay bị filter vào spam.
3.2 Email Receiving Flow (Luồng nhận email)
sequenceDiagram participant Remote as Sender's SMTP<br/>Server participant DNS as DNS Server participant LB as Load Balancer participant SMTP as SMTP Inbound<br/>Server participant SPF as SPF/DKIM/DMARC<br/>Validator participant Spam as Spam Filter<br/>(ML Model) participant Virus as Virus Scanner<br/>(ClamAV) participant Router as Email Router participant Thread as Threading<br/>Service participant Meta as Metadata DB participant Body as Body Store participant Attach as Attachment<br/>Store (S3) participant Index as Search Indexer participant Notify as Notification<br/>Service Remote->>DNS: MX lookup for our domain DNS-->>Remote: MX: mail.ourdomain.com Remote->>LB: TCP connection (port 25) LB->>SMTP: Forward to SMTP server SMTP-->>Remote: 220 mail.ourdomain.com ESMTP Remote->>SMTP: EHLO sender.com SMTP-->>Remote: 250-SIZE 35882577<br/>250-STARTTLS Remote->>SMTP: MAIL FROM:<alice@sender.com> Note over SMTP,SPF: Quick SPF check<br/>before accepting SMTP->>SPF: Check SPF record for sender.com SPF-->>SMTP: SPF PASS SMTP-->>Remote: 250 OK Remote->>SMTP: RCPT TO:<hieu@ourdomain.com> Note over SMTP: Verify recipient exists SMTP-->>Remote: 250 OK Remote->>SMTP: DATA + email content SMTP-->>Remote: 250 OK — Queued Note over SMTP: Email accepted.<br/>Now async processing begins. SMTP->>SPF: Full DKIM + DMARC validation SPF-->>SMTP: DKIM PASS, DMARC PASS SMTP->>Spam: Score this email Spam-->>SMTP: Score: 2.1 (threshold: 5.0)<br/>NOT SPAM SMTP->>Virus: Scan attachments Virus-->>SMTP: CLEAN SMTP->>Router: Route to mailbox Router->>Thread: Find/create conversation Note over Thread: Match Message-ID,<br/>In-Reply-To, References,<br/>Subject headers Thread->>Meta: Save metadata<br/>(subject, from, to, labels,<br/>conversation_id) Thread->>Body: Save email body<br/>(HTML + plain text) Thread->>Attach: Save attachments to S3 Thread->>Index: Queue for search indexing Thread->>Notify: Notify user (push + badge)
Receiving Flow — Chi tiết từng bước
Bước 1: MX Record Lookup
- Sender’s SMTP server query DNS: “Mail server nào nhận email cho
ourdomain.com?” - DNS trả về MX records:
mail1.ourdomain.com— priority 10 (primary)mail2.ourdomain.com— priority 20 (backup)
- Sender kết nối đến server có priority thấp nhất (số nhỏ = ưu tiên cao)
Bước 2: SMTP Inbound accepts email
- Load balancer (L4 — TCP level) phân phối connection đến SMTP inbound servers
- SMTP server thực hiện initial checks trước khi accept:
- Connection rate limiting: chống DDoS, max connections per IP
- Reverse DNS check: sender IP có reverse DNS record không?
- RBL check (Realtime Blackhole List): sender IP có trong blacklist không?
- SPF quick check: sender có quyền gửi từ domain đó không?
- Recipient validation: user
hieu@ourdomain.comcó tồn tại không?
- Nếu tất cả pass →
250 OK, email vào processing queue - Nếu fail →
550 User not foundhoặc554 Transaction failed
Bước 3: SPF/DKIM/DMARC Validation (Chi tiết ở Section 4 — Security)
- SPF: Kiểm tra sender’s IP có được authorize bởi domain’s DNS record không
- DKIM: Verify chữ ký số trong email header → email không bị tamper
- DMARC: Policy kết hợp SPF + DKIM → quyết định reject/quarantine/none
Bước 4: Spam Filter
- Email được scoring bởi ML model (chi tiết ở Section 3.6)
- Score thấp (< 5.0) → Inbox
- Score trung bình (5.0 - 8.0) → Spam folder
- Score cao (> 8.0) → Reject silently
Bước 5: Virus Scanning
- Tất cả attachments được scan bởi antivirus engine (ClamAV hoặc tương tự)
- Nếu phát hiện malware → quarantine email, notify admin
- Scan cả embedded links trong HTML body → phishing detection
Bước 6: Email Routing & Storage
- Router xác định mailbox destination dựa trên:
- Recipient address → user’s mailbox
- User rules (filters): “Email từ boss@company.com → label: Important”
- Server rules: mailing list expansion, alias resolution, forwarding
- Threading service group email vào conversation (chi tiết ở Section 3.8)
- Lưu metadata, body, attachments vào respective stores
Bước 7: Post-processing (Async)
- Search indexer: index email content cho full-text search
- Notification service: push notification đến mobile, update badge count
- Analytics: track delivery metrics
3.3 Email Reading Flow (Luồng đọc email)
Via REST API (Web/Mobile clients):
| API Endpoint | Mô tả | Data Source |
|---|---|---|
GET /api/v1/inbox?page=1&limit=50 | Lấy danh sách inbox | Cache (Redis) → Metadata DB |
GET /api/v1/emails/{id} | Đọc nội dung 1 email | Body Store + Attachment Store |
GET /api/v1/emails/{id}/attachments/{aid} | Download attachment | Attachment Store (S3) via CDN |
GET /api/v1/search?q=keyword | Tìm kiếm email | Search Index (Elasticsearch) |
PUT /api/v1/emails/{id}/read | Đánh dấu đã đọc | Metadata DB + Cache invalidation |
PUT /api/v1/emails/{id}/labels | Thêm/xóa labels | Metadata DB |
DELETE /api/v1/emails/{id} | Xóa email (move to trash) | Metadata DB (soft delete) |
GET /api/v1/conversations/{cid} | Xem conversation thread | Metadata DB (query by conversation_id) |
Via IMAP (Desktop clients):
- IMAP server đọc từ cùng Metadata DB + Body Store
- Hỗ trợ IMAP commands: SELECT (chọn folder), FETCH (lấy email), SEARCH (tìm), STORE (flags), COPY (copy to folder)
- IMAP IDLE: persistent connection, server push notification khi có email mới → real-time experience
Caching Strategy cho Inbox:
- Cache what: Inbox metadata (subject, sender, date, snippet, read/unread) — không cache full body
- Cache key:
user:{user_id}:inbox:page:{n} - Invalidation: khi có email mới, hoặc user thay đổi label/read status
- TTL: 5 minutes (short TTL vì inbox thay đổi thường xuyên)
- Cache-aside pattern: check cache → miss → query DB → populate cache
3.4 Email Storage Architecture
Đây là quyết định kiến trúc quan trọng nhất cho email system. Email data có 3 loại rất khác nhau:
Traditional Storage (Legacy)
| Phương pháp | Mô tả | Nhược điểm |
|---|---|---|
| mbox | Tất cả email trong 1 file lớn, nối tiếp nhau | File lock contention, corruption risk, không scale |
| Maildir | Mỗi email 1 file riêng, organized trong directories | Quá nhiều files → filesystem metadata overhead, không efficient cho search |
Verdict: Cả hai đều không phù hợp cho distributed system 1B users. Cần giải pháp hiện đại.
Modern Storage Architecture
flowchart TB subgraph "Storage Architecture" direction TB subgraph "Metadata Store — Cassandra/Bigtable" M1["Row Key: user_id + email_id<br/>Columns: subject, from, to, cc, bcc,<br/>date, labels[], is_read, is_starred,<br/>conversation_id, snippet,<br/>attachment_ids[], size"] M2["Secondary Index:<br/>user_id + label + date (DESC)<br/>→ Inbox query nhanh"] M3["Secondary Index:<br/>user_id + conversation_id<br/>→ Thread grouping"] end subgraph "Email Body Store — Distributed Blob" B1["Key: email_id<br/>Value: HTML body + plain text<br/>Compressed with zstd"] B2["Average size: 5-50 KB<br/>(after compression)"] B3["Deduplication:<br/>Content-hash based"] end subgraph "Attachment Store — S3/GCS" A1["Key: content_hash<br/>Value: raw file bytes"] A2["Content-addressable:<br/>same file = same hash = store once"] A3["Virus scanned before store"] A4["CDN for download<br/>Pre-signed URLs (time-limited)"] end subgraph "Search Index — Elasticsearch" S1["Index per user shard<br/>Fields: subject, body_text,<br/>from, to, date, labels,<br/>attachment_names, has_attachment"] end subgraph "Cache — Redis Cluster" C1["Inbox metadata cache<br/>Recent 200 emails per user"] C2["Unread count per folder"] C3["User settings & preferences"] end end style M1 fill:#4CAF50,stroke:#333,color:#fff style B1 fill:#2196F3,stroke:#333,color:#fff style A1 fill:#FF9800,stroke:#333,color:#fff style S1 fill:#9C27B0,stroke:#333,color:#fff style C1 fill:#f44336,stroke:#333,color:#fff
Tại sao Cassandra/Bigtable cho Metadata?
| Yêu cầu | Cassandra/Bigtable đáp ứng |
|---|---|
| Write-heavy (nhận email liên tục) | Append-only LSM-tree → write performance tuyệt vời |
| Read pattern: “emails of user X, sorted by date DESC” | Partition key = user_id, clustering key = date DESC → sequential read |
| Scale to 1B users | Horizontal scaling, consistent hashing |
| High availability | Multi-datacenter replication, tunable consistency |
| Schema flexibility | Wide-column model, dễ thêm columns (labels, flags) |
Data Model (Cassandra):
| Column | Type | Mô tả |
|---|---|---|
user_id | UUID (Partition Key) | Shard by user — tất cả email của 1 user nằm cùng partition |
folder | TEXT (Clustering Key 1) | inbox, sent, drafts, trash, spam, custom |
email_date | TIMESTAMP (Clustering Key 2, DESC) | Sort mới nhất trước |
email_id | UUID (Clustering Key 3) | Unique identifier |
subject | TEXT | Tiêu đề |
from_address | TEXT | Người gửi |
to_addresses | LIST | Người nhận |
snippet | TEXT | 100 ký tự đầu cho preview |
is_read | BOOLEAN | Đã đọc chưa |
is_starred | BOOLEAN | Đánh dấu sao |
labels | SET | Labels (Important, Work, …) |
conversation_id | UUID | Nhóm conversation |
attachment_ids | LIST | Tham chiếu đến blob store |
body_ref | TEXT | Reference đến body store |
size_bytes | INT | Kích thước email (tính storage quota) |
Tại sao không dùng PostgreSQL/MySQL? Relational DB đủ tốt cho < 10M users. Nhưng ở 1B users, mỗi user trung bình 10K emails → 10 nghìn tỷ rows. Sharding relational DB phức tạp hơn nhiều so với Cassandra vốn đã designed cho horizontal scaling.
Attachment Deduplication
Attachment chiếm phần lớn storage cost. Kỹ thuật deduplication rất quan trọng:
- Khi upload attachment → compute SHA-256 hash của file content
- Check hash trong metadata: đã tồn tại chưa?
- Đã có: không upload lại, chỉ tạo reference mới → tiết kiệm storage
- Chưa có: upload lên S3, lưu hash + S3 key
- Reference counting: mỗi attachment có counter “bao nhiêu emails reference nó”
- Khi xóa email → giảm counter. Counter = 0 → garbage collect (xóa khỏi S3)
Ví dụ thực tế: CEO gửi file báo cáo 10MB cho 500 nhân viên. Thay vì lưu 500 x 10MB = 5GB, hệ thống chỉ lưu 1 x 10MB + 500 references. Tiết kiệm 99.8% storage cho case này.
3.5 Email Search — Full-text Search at Scale
Search là tính năng quan trọng thứ hai sau read/write. User kỳ vọng tìm kiếm email nhanh như Google Search.
Search Architecture
flowchart TB subgraph "Search Flow" direction TB USER["User: Search 'invoice Q3'"] API["API Service"] PARSE["Query Parser<br/>→ Parse 'invoice Q3' thành<br/>structured query"] ES["Elasticsearch Cluster<br/>(Sharded by user_id)"] META["Metadata DB<br/>(Cassandra)"] RESULT["Return results:<br/>email_id, subject, snippet,<br/>date, highlighted matches"] end USER --> API API --> PARSE PARSE --> ES ES --> META META --> RESULT RESULT --> USER subgraph "Indexing Flow (Async)" direction TB NEW_EMAIL["New Email Arrives"] QUEUE["Index Queue (Kafka)"] INDEXER["Search Indexer<br/>(Extract text from HTML,<br/>extract attachment names,<br/>strip formatting)"] ES2["Elasticsearch<br/>Index Document"] end NEW_EMAIL --> QUEUE QUEUE --> INDEXER INDEXER --> ES2 style ES fill:#9C27B0,stroke:#333,color:#fff style PARSE fill:#2196F3,stroke:#333,color:#fff
Indexing Strategy
| Quyết định | Lựa chọn | Lý do |
|---|---|---|
| Index granularity | Per-user sharding | Query luôn filter by user_id → co-locate tất cả emails của 1 user |
| Index fields | subject, body_text, from, to, attachment_names, date, labels | Cover mọi search use case |
| Analyzer | Standard analyzer + ngram cho autocomplete | Hỗ trợ partial match (“inv” → “invoice”) |
| Update strategy | Async indexing via Kafka queue | Không block email delivery, chấp nhận vài giây delay |
| Replication | 1 primary + 1 replica per shard | Availability + read throughput |
Search Latency Requirements
| Loại search | Target latency | Kỹ thuật |
|---|---|---|
| Simple keyword | < 200ms | Inverted index lookup |
| Full-text với operators (AND, OR, NOT) | < 500ms | Boolean query |
| Date range + keyword | < 500ms | Composite query (date filter + text match) |
| Attachment name search | < 500ms | Indexed field |
| Autocomplete (search-as-you-type) | < 100ms | Edge n-gram tokenizer + prefix query |
Tại sao Elasticsearch/Lucene?
- Inverted index: Lucene’s core data structure — map từ “term” đến list of documents chứa term đó. Rất nhanh cho text search.
- Relevance scoring: TF-IDF / BM25 → kết quả relevant nhất hiện trước
- Horizontal scaling: Shard by user_id range → mỗi shard handle subset of users
- Near real-time: Index refresh mỗi 1 giây → email mới searchable gần ngay lập tức
- Rich query DSL: Boolean queries, phrase match, fuzzy match, date range, aggregations
Hieu nhớ: Search index là derived data — nó được build từ primary data (Metadata DB + Body Store). Nếu index bị corrupt → rebuild từ primary data. Không bao giờ dùng search index làm source of truth.
Search Index Size Estimation
Với 1B users, mỗi user trung bình 10K emails:
20 PB search index — đây là lý do phải shard by user_id. Mỗi Elasticsearch node handle ~1TB data hiệu quả → cần ~20,000 nodes cho search cluster (thực tế Google/Gmail dùng custom search engine, không dùng vanilla Elasticsearch).
3.6 Spam Filtering — Lọc thư rác
Spam là thách thức lớn nhất của email system. Theo thống kê, 80-85% email traffic toàn cầu là spam.
Evolution of Spam Filtering
| Thế hệ | Phương pháp | Ưu điểm | Nhược điểm |
|---|---|---|---|
| Gen 1 | Rule-based (keyword matching) | Đơn giản, transparent | Dễ bypass (thay “viagra” thành “v1agra”) |
| Gen 2 | Bayesian filter (SpamAssassin) | Statistical, tự học | Cần training data per user, slow adapt |
| Gen 3 | ML-based (SVM, Random Forest) | Accurate hơn, feature engineering | Cần labeled data, model maintenance |
| Gen 4 | Deep Learning (BERT, Transformers) | State-of-the-art accuracy | Expensive compute, latency cao |
| Gen 5 | Ensemble + Reputation | Kết hợp nhiều signals | Complex pipeline |
Spam Filtering Pipeline
Email đi qua nhiều layers filtering, mỗi layer bắt loại spam khác nhau:
Layer 1 — Connection-level filtering (trước khi nhận email):
- IP reputation check: sender IP có trong blacklist (Spamhaus, SORBS) không?
- Reverse DNS check: IP có valid PTR record không?
- Connection rate limiting: 1 IP gửi quá nhiều connections/phút → block
- Greylisting: lần đầu gửi → reject với “try again later”. Legit server sẽ retry, spam server thường không retry
Layer 2 — Envelope-level filtering (SMTP session):
- SPF check: sender IP authorized cho domain?
- Sender reputation: domain đã gửi bao nhiêu spam trước đây?
- Recipient validation: user có tồn tại không? (chống dictionary attack)
Layer 3 — Header-level filtering (sau khi nhận DATA):
- DKIM verification: chữ ký số hợp lệ không?
- DMARC policy: domain owner muốn làm gì khi SPF/DKIM fail?
- Header analysis: forged headers? Inconsistent routing?
Layer 4 — Content-level filtering (ML model):
- Text analysis: Bayesian scoring trên subject + body text
- URL analysis: links có dẫn đến known phishing sites không?
- Image analysis: embedded images có chứa spam text không? (image spam)
- Attachment analysis: file type suspicious? (.exe, .scr trong .zip?)
- HTML analysis: hidden text? Tiny fonts? Misleading links?
Layer 5 — User-level filtering:
- User feedback: user mark as spam → train personal model
- Contact list: email từ known contacts → boost trust score
- User rules: custom filters set by user
Spam Score Calculation
Trong đó:
- = feature function thứ (SPF result, DKIM result, Bayesian score, URL score, …)
- = weight của feature (learned từ training data)
- Threshold mặc định: score > 5.0 → spam
SPF/DKIM/DMARC — Bộ ba authentication
| Protocol | Mục đích | Cơ chế |
|---|---|---|
| SPF (Sender Policy Framework) | Kiểm tra IP có quyền gửi email cho domain | Domain publish DNS TXT record: “v=spf1 ip4:192.168.1.0/24 include:_spf.google.com -all” |
| DKIM (DomainKeys Identified Mail) | Verify email không bị thay đổi trên đường truyền | Sender ký email bằng private key, receiver verify bằng public key (lấy từ DNS) |
| DMARC (Domain-based Message Authentication) | Policy: làm gì khi SPF/DKIM fail | Domain publish policy: “reject” (từ chối), “quarantine” (vào spam), “none” (chỉ report) |
Hieu nhớ: SPF chống domain spoofing (ai đó giả làm your domain). DKIM chống email tampering (ai đó sửa nội dung). DMARC là policy layer kết hợp cả hai. Cả 3 đều dùng DNS records — zero infrastructure cost.
3.7 Attachment Handling — Xử lý file đính kèm
Upload Flow
- Client gửi
POST /api/v1/attachments/upload(multipart/form-data) - API server validate:
- File size: max 25MB per file, max 25MB total per email
- File type: block dangerous types (.exe, .bat, .cmd, .scr) hoặc scan kỹ hơn
- Virus scanning: scan file trước khi store
- ClamAV (open-source) hoặc commercial engine
- Nếu infected → reject upload, notify user
- Compute SHA-256 hash → check deduplication
- Upload to S3/GCS với server-side encryption (AES-256)
- Return
attachment_id+content_hashcho client - Client đính kèm
attachment_idvào email khi gửi
Download Flow
- Client request
GET /api/v1/emails/{id}/attachments/{aid} - API server generate pre-signed URL (time-limited, 15 phút)
- Pre-signed URL = URL + temporary signature → S3 cho download trực tiếp mà không qua API server
- Giảm tải cho API server, tận dụng CDN
- Client download trực tiếp từ CDN/S3
Inline vs Download Attachments
| Loại | Khi nào | Xử lý |
|---|---|---|
| Inline | Images trong email body (<img src="cid:...">) | Embed trong HTML, load cùng email body |
| Download | Files đính kèm (PDF, DOCX, ZIP) | Hiện icon + file name, user click để download |
| Preview | Images, PDFs | Generate thumbnail (async), hiện preview không cần download full file |
Storage Tiering cho Attachments
| Tier | Storage Class | Khi nào | Chi phí |
|---|---|---|---|
| Hot | S3 Standard | < 30 ngày tuổi | Cao nhất |
| Warm | S3 Infrequent Access | 30-180 ngày | Thấp hơn 40% |
| Cold | S3 Glacier Instant Retrieval | 180 ngày - 2 năm | Thấp hơn 68% |
| Archive | S3 Glacier Deep Archive | > 2 năm | Thấp nhất (90%+) |
Tại sao tiering quan trọng? Attachment storage chiếm 80%+ total cost của email system. User hiếm khi access attachment cũ hơn 30 ngày. Lifecycle policy tự động chuyển attachment xuống tier rẻ hơn → tiết kiệm hàng triệu USD/năm ở scale 1B users.
3.8 Threading/Conversation — Nhóm email thành cuộc hội thoại
Gmail-style conversation view là tính năng user yêu thích nhất. Thay vì hiển thị mỗi email riêng lẻ, group tất cả reply/forward thành 1 conversation.
Cơ chế: Email Headers
Mỗi email có các headers quan trọng cho threading:
| Header | Mô tả | Ví dụ |
|---|---|---|
Message-ID | Unique ID cho mỗi email | <abc123@mail.google.com> |
In-Reply-To | Message-ID của email đang reply | <xyz789@mail.google.com> |
References | Chuỗi Message-IDs từ đầu conversation | <first@...> <second@...> <third@...> |
Subject | Tiêu đề (dùng làm fallback matching) | Re: Project Update |
Conversation Grouping Algorithm
-
Primary matching (chính xác nhất):
- Khi nhận email mới, check header
In-Reply-TovàReferences - Tìm email có
Message-IDmatch → cùng conversation
- Khi nhận email mới, check header
-
Fallback matching (khi headers bị thiếu/corrupt):
- Normalize subject: bỏ
Re:,Fwd:,FW:, trim whitespace - Match by:
normalized_subject+sender/recipient overlap+time proximity(trong 30 ngày)
- Normalize subject: bỏ
-
Conversation ID assignment:
- Email đầu tiên trong thread → tạo
conversation_idmới - Email reply → kế thừa
conversation_idtừ parent email - Lưu
conversation_idtrong metadata → query tất cả emails trong conversation bằng 1 query
- Email đầu tiên trong thread → tạo
-
Display ordering:
- Trong conversation view: sort by
date ASC(cũ nhất trước, mới nhất cuối) - Trong inbox: conversation hiển thị theo date của email mới nhất trong conversation
- Trong conversation view: sort by
Edge cases phức tạp:
- Email clients cũ không set
In-Reply-To→ phải fallback sang subject matching- Subject bị thay đổi giữa chừng (“Re: Project” → “Re: Project — URGENT”) → fuzzy matching
- Fork conversation (reply-all rồi ai đó reply riêng) → có thể split thành sub-threads
3.9 Sync Across Devices — Đồng bộ đa thiết bị
User kỳ vọng: đọc email trên phone → laptop hiện “đã đọc” ngay lập tức.
IMAP IDLE — Server Push cho Desktop Clients
IMAP IDLE là extension cho phép server push notification khi có thay đổi:
- Client gửi
IDLEcommand → connection treo, chờ - Server detect email mới/flag change → gửi
* EXISTShoặc* FETCHnotification - Client nhận notification → fetch changes
- Timeout sau 29 phút → client gửi
IDLElại (RFC 2177)
Hạn chế: IDLE chỉ monitor 1 folder tại 1 thời điểm. Muốn monitor inbox + sent + drafts → cần 3 persistent connections.
Delta Sync cho Mobile/Web (API-based)
Mobile clients không dùng IMAP mà dùng delta sync qua REST API:
- Client giữ
last_sync_timestamp(hoặcsync_token) - Khi sync:
GET /api/v1/sync?since={last_sync_timestamp} - Server trả về list of changes kể từ timestamp đó:
- New emails (email_id, metadata)
- Flag changes (email_id → read/unread/starred)
- Deleted emails (email_id → deleted)
- Label changes (email_id → labels added/removed)
- Client apply changes locally → update
last_sync_timestamp
Ưu điểm so với IMAP IDLE:
- Bandwidth efficient: chỉ gửi delta (thay đổi), không gửi full state
- Battery friendly: không cần persistent connection, sync on-demand hoặc periodic
- Offline support: áp dụng local changes → sync khi có mạng
Offline Support
| Tính năng | Online | Offline |
|---|---|---|
| Read cached emails | Server | Local SQLite/IndexedDB |
| Compose email | Send ngay | Queue locally, send khi online |
| Search | Server-side (Elasticsearch) | Local search (cached emails only) |
| Mark read/starred | Sync ngay | Queue change, sync later |
| Download attachment | S3/CDN | Không available (trừ khi đã cache) |
Conflict resolution (khi offline changes conflict với server):
- Last-writer-wins cho simple flags (read/unread): timestamp lớn hơn thắng
- Union merge cho labels: merge tất cả labels từ cả client và server
- Server-wins cho destructive actions (delete): nếu server đã xóa, client phải accept
Capacity Estimation (Ước lượng dung lượng)
Assumptions (Giả thiết)
| Thông số | Giá trị | Giải thích |
|---|---|---|
| Total users | 1B (1 tỷ) | Gmail-scale |
| DAU (Daily Active Users) | 500M | 50% of total users |
| Emails received per user per day | 40 | Bao gồm newsletters, notifications, personal |
| Emails sent per user per day | 5 | User gửi ít hơn nhận nhiều |
| Spam ratio | 80% | 80% incoming email là spam (bị filter trước khi đến mailbox) |
| Avg email metadata size | 1 KB | Subject, from, to, headers, flags |
| Avg email body size | 50 KB | HTML + plain text (before compression) |
| Avg email body compressed | 10 KB | zstd compression ~5x ratio cho HTML |
| % emails có attachment | 20% | 1 in 5 emails |
| Avg attachment size | 500 KB | Mix of small images + large documents |
| Max attachment size per email | 25 MB | Standard limit |
| Retention | Vĩnh viễn (tối thiểu 7 năm cho compliance) | Email là business record |
Email Volume per Day
QPS (Queries Per Second)
Incoming SMTP QPS
Chú ý: Đây là trước spam filter. Phần lớn sẽ bị reject ở connection-level (Layer 1-2), không cần full processing.
Legitimate Email Processing QPS
Email Read QPS (API/IMAP)
Giả sử mỗi DAU check email 10 lần/ngày, mỗi lần load 20 emails:
Read QPS cao hơn write QPS 12x — cần heavy caching cho inbox metadata.
Search QPS
Giả sử 10% DAU search ít nhất 1 lần/ngày, trung bình 3 searches:
Storage Estimation
Metadata Storage (per year)
Email Body Storage (per year)
Attachment Storage (per year)
Alert: Attachment storage chiếm 90%+ total storage. Deduplication + tiering là bắt buộc.
Giả sử deduplication ratio 30% (nhiều email cùng attachment — company-wide emails, forwarded files):
Search Index Size
Accumulated over 7 years:
Total Storage Summary
| Component | Per Year | 7-Year Total | % of Total |
|---|---|---|---|
| Metadata (Cassandra) | 2.92 PB | 20.4 PB | 1.3% |
| Email Body (Blob) | 29.2 PB | 204.4 PB | 13.1% |
| Attachments (S3) | 204.4 PB | 1,430.8 PB (~1.4 EB) | 79.4% (!) |
| Search Index (ES) | 5.84 PB | 40.9 PB | 2.6% |
| Cache (Redis) | ~50 TB | ~50 TB | ~0% |
| Total | ~242 PB | ~1,696 PB (~1.7 EB) | 100% |
Bandwidth Estimation
Bandwidth cho attachment downloads sẽ do CDN/S3 handle trực tiếp, không qua application servers.
Tóm tắt Estimation
| Metric | Value |
|---|---|
| Total incoming emails/day (incl. spam) | 40B |
| Legitimate emails/day | 8B |
| Outgoing emails/day | 2.5B |
| Incoming SMTP QPS (peak) | ~1.39M/s |
| Email read QPS (peak) | ~3.47M/s |
| Search QPS (peak) | ~8.7K/s |
| Total storage/year | ~242 PB |
| Attachment storage/year | ~204 PB (84% of total) |
| Search index (7-year) | ~41 PB |
| Incoming bandwidth | ~45 Gbps |
Security — Bảo mật Email
Email là attack vector số 1 cho cyber attacks. Theo IBM, 90%+ of cyberattacks bắt đầu bằng phishing email.
5.1 Encryption Layers
In-transit Encryption (TLS/STARTTLS)
| Layer | Protocol | Mô tả |
|---|---|---|
| Client → Our SMTP server | TLS 1.3 (port 465) hoặc STARTTLS (port 587) | Bắt buộc cho authenticated submission |
| Our SMTP → Receiver’s SMTP | STARTTLS (opportunistic) | Best-effort — nếu receiver không hỗ trợ TLS, có thể fallback plaintext |
| Client → API server | HTTPS (TLS 1.3) | Bắt buộc cho web/mobile clients |
Vấn đề với STARTTLS: Đây là opportunistic encryption — nếu MITM attacker strip STARTTLS capability, email gửi plaintext. Giải pháp: MTA-STS (RFC 8461) — domain publish policy yêu cầu TLS bắt buộc.
At-rest Encryption
| Data | Encryption | Key Management |
|---|---|---|
| Email body | AES-256-GCM | Per-user encryption key, managed by KMS |
| Attachments (S3) | SSE-S3 hoặc SSE-KMS | AWS KMS managed keys |
| Metadata (Cassandra) | Transparent Data Encryption (TDE) | Cluster-level key |
| Search Index | Encrypted at filesystem level | Node-level encryption |
End-to-End Encryption (E2EE)
Cho những trường hợp yêu cầu bảo mật cao nhất (healthcare, legal, finance):
| Protocol | Cơ chế | Adoption |
|---|---|---|
| PGP (Pretty Good Privacy) | Public/private key pair. Sender encrypt với receiver’s public key. | Low (khó dùng cho non-technical users) |
| S/MIME | Certificate-based. Tương tự PGP nhưng dùng X.509 certificates. | Medium (enterprise — Outlook supports natively) |
| Provider E2EE (ProtonMail) | Provider manages key exchange, user giữ private key | Growing (nhưng chỉ trong ecosystem) |
Trade-off lớn nhất của E2EE: Server không thể đọc email content → không thể search server-side, không thể scan spam/virus. User phải chấp nhận search chỉ hoạt động trên client-side (đã decrypt).
5.2 Email Authentication — SPF/DKIM/DMARC (Chi tiết)
SPF (Sender Policy Framework)
Mục đích: Ngăn ai đó gửi email giả danh domain của bạn.
Cơ chế: Domain owner publish DNS TXT record liệt kê IP/server nào được phép gửi email:
ourdomain.com. IN TXT "v=spf1 ip4:192.168.1.0/24 include:_spf.google.com include:sendgrid.net -all"
Giải thích: Chỉ IP range 192.168.1.0/24, Google’s servers, và SendGrid được phép gửi email từ @ourdomain.com. -all = tất cả IP khác → FAIL.
Receiver check: Khi nhận email FROM: user@ourdomain.com → query DNS → check sender IP có trong SPF record không.
DKIM (DomainKeys Identified Mail)
Mục đích: Verify email không bị thay đổi trên đường truyền.
Cơ chế:
- Sender’s SMTP server tạo digital signature của email headers + body bằng private key
- Signature được thêm vào email header
DKIM-Signature - Public key published trong DNS TXT record
- Receiver query DNS → lấy public key → verify signature
Nếu signature invalid → email bị tamper trên đường truyền (hoặc DKIM misconfigured).
DMARC (Domain-based Message Authentication, Reporting & Conformance)
Mục đích: Cho domain owner quyết định: “Khi SPF/DKIM fail, làm gì với email đó?”
DNS record:
_dmarc.ourdomain.com. IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc@ourdomain.com; pct=100"
Policies:
p=none→ chỉ report, không action (dùng khi mới setup, monitor)p=quarantine→ đưa vào spam folderp=reject→ từ chối hoàn toàn (strongest protection)
5.3 Anti-Phishing & Malware Defense
| Layer | Kỹ thuật | Mô tả |
|---|---|---|
| URL scanning | Reputation database + real-time check | Check mọi URL trong email body against known phishing domains |
| Lookalike domain detection | Punycode analysis, visual similarity | Detect goog1e.com (thay l bằng 1) hoặc Unicode homograph attacks |
| Attachment scanning | Multi-engine AV + sandboxing | Scan file bằng nhiều AV engines. Suspicious files chạy trong sandbox để detect behavior |
| Display name spoofing | Header analysis | Detect “CEO Name hacker@evil.com” — display name giả nhưng email thật khác |
| Business Email Compromise | ML model | Detect unusual patterns: “CEO suddenly asking for wire transfer” |
5.4 Data Loss Prevention (DLP)
Ngăn chặn rò rỉ dữ liệu nhạy cảm qua email:
| Rule | Action | Ví dụ |
|---|---|---|
| Email chứa credit card numbers (regex match) | Block send + alert admin | Employee vô tình gửi file chứa PII |
| Email chứa SSN/CMND patterns | Quarantine + require approval | HR gửi nhầm file employee data |
| Attachment > threshold size to external domain | Require approval | Ngăn data exfiltration |
| Sensitive label + external recipient | Block + notify | Document “Confidential” gửi ra ngoài company |
Liên quan: Xem thêm Tuan-15-Data-Security-Encryption cho chi tiết về encryption strategies và Tuan-14-AuthN-AuthZ-Security cho authentication patterns.
DevOps — Monitoring & Observability
6.1 Key Metrics cần Monitor
Delivery Metrics
| Metric | Mô tả | Target | Alert Threshold |
|---|---|---|---|
| Delivery rate | % emails delivered thành công | > 99% | < 98% |
| Bounce rate | % emails bị bounce (hard + soft) | < 2% | > 5% |
| Hard bounce rate | % emails bounce vì invalid address | < 0.5% | > 1% |
| Soft bounce rate | % emails bounce vì temporary failure | < 1.5% | > 3% |
| Spam complaint rate | % recipients mark as spam | < 0.1% | > 0.3% |
| Delivery latency (p99) | Thời gian từ send đến delivered | < 30s | > 60s |
Processing Metrics
| Metric | Mô tả | Target | Alert Threshold |
|---|---|---|---|
| Incoming SMTP QPS | Emails/second nhận vào | Monitor trend | Spike > 2x normal |
| Spam filter accuracy | True positive rate | > 99.5% | < 99% |
| Spam false positive rate | Legit email bị đánh spam | < 0.01% | > 0.05% |
| Virus detection rate | Infected emails caught | > 99.9% | < 99.5% |
| Processing pipeline latency (p99) | Từ SMTP accept đến mailbox | < 10s | > 30s |
| Queue depth | Messages waiting in Kafka | Monitor trend | > 1M messages |
Search Metrics
| Metric | Mô tả | Target | Alert Threshold |
|---|---|---|---|
| Search latency (p50) | Median search response time | < 100ms | > 200ms |
| Search latency (p99) | 99th percentile | < 500ms | > 1000ms |
| Index lag | Time from email received to searchable | < 5s | > 30s |
| Search availability | % of time search is responsive | > 99.9% | < 99.5% |
Infrastructure Metrics
| Metric | Mô tả | Alert Threshold |
|---|---|---|
| Cassandra read latency (p99) | Metadata query latency | > 50ms |
| S3 request error rate | Failed attachment operations | > 0.1% |
| Redis cache hit rate | Inbox cache effectiveness | < 90% |
| Elasticsearch cluster health | Green/Yellow/Red status | Yellow > 5min |
| Kafka consumer lag | Messages behind in processing | > 100K per partition |
| SMTP connection pool utilization | Outbound connections in use | > 80% |
6.2 Alerting Strategy
| Severity | Response Time | Ví dụ |
|---|---|---|
| P0 — Critical | < 5 phút | Email delivery completely stopped, data loss detected |
| P1 — High | < 15 phút | Delivery rate drop > 10%, spam filter down |
| P2 — Medium | < 1 giờ | Search latency degraded, queue depth growing |
| P3 — Low | Next business day | Cache hit rate drop, storage approaching threshold |
6.3 Dashboards cần có
- Email Flow Dashboard: real-time incoming/outgoing QPS, delivery success rate, bounce rate
- Spam Dashboard: spam volume, filter accuracy, false positive count, top spam sources
- Search Dashboard: search QPS, latency percentiles, index lag, cluster health
- Storage Dashboard: total storage by type, growth trend, storage tiering distribution
- Security Dashboard: SPF/DKIM/DMARC pass rates, phishing attempts blocked, malware detected
- User Experience Dashboard: inbox load time, email open latency, sync latency
Liên quan: Xem thêm Tuan-13-Monitoring-Observability cho observability patterns (logs, metrics, traces).
Mermaid Diagrams — Tổng hợp Architecture
Email Sending Flow (Simplified)
flowchart LR A["Sender<br/>(compose email)"] --> B["API Server<br/>(validate, rate limit)"] B --> C["Outgoing Queue<br/>(Kafka)"] C --> D["SMTP Outbound<br/>(DKIM sign)"] D --> E["DNS Lookup<br/>(MX record)"] E --> F["Receiver's SMTP<br/>Server"] F --> G["Receiver's<br/>Processing Pipeline"] G --> H["Receiver's<br/>Mailbox"] D -->|"Bounce"| I["Bounce Handler<br/>(notify sender)"] C -->|"Retry"| D style C fill:#FF9800,stroke:#333,color:#fff style D fill:#4CAF50,stroke:#333,color:#fff style F fill:#2196F3,stroke:#333,color:#fff
Email Receiving Pipeline (Detailed)
flowchart TB A["External SMTP Server"] --> B["Load Balancer<br/>(L4 TCP)"] B --> C["SMTP Inbound Server"] C --> D{"Connection-level<br/>Check"} D -->|"IP blacklisted"| REJECT1["550 Blocked"] D -->|"Pass"| E{"SPF Check"} E -->|"Fail + DMARC reject"| REJECT2["550 SPF Failed"] E -->|"Pass/Softfail"| F["Accept DATA"] F --> G["Incoming Queue<br/>(Kafka)"] G --> H["DKIM Verify"] H --> I["DMARC Evaluate"] I --> J["Spam Filter<br/>(ML Scoring)"] J -->|"Score > 8.0"| SPAM_REJECT["Silently Discard"] J -->|"Score 5.0 - 8.0"| SPAM_FOLDER["→ Spam Folder"] J -->|"Score < 5.0"| K["Virus Scanner"] K -->|"Infected"| QUARANTINE["Quarantine<br/>(alert admin)"] K -->|"Clean"| L["Email Router"] L --> M["Apply User Rules<br/>(filters, labels)"] M --> N["Threading Service<br/>(conversation grouping)"] N --> O["Save Metadata<br/>(Cassandra)"] N --> P["Save Body<br/>(Blob Store)"] N --> Q["Save Attachments<br/>(S3)"] N --> R["Queue for Search<br/>Indexing"] N --> S["Push Notification<br/>(FCM/APNs)"] R --> T["Elasticsearch<br/>Indexer"] style G fill:#FF9800,stroke:#333,color:#fff style J fill:#f44336,stroke:#333,color:#fff style T fill:#9C27B0,stroke:#333,color:#fff style O fill:#4CAF50,stroke:#333,color:#fff
Storage Architecture Overview
flowchart TB subgraph "Write Path" W1["Email arrives"] --> W2["Processing Pipeline"] W2 --> W3["Metadata Writer"] W2 --> W4["Body Writer"] W2 --> W5["Attachment Writer"] W2 --> W6["Index Writer"] end subgraph "Storage Engines" W3 --> META[("Cassandra Cluster<br/>────────────<br/>Partition: user_id<br/>Sort: date DESC<br/>────────────<br/>3 replicas<br/>Consistency: LOCAL_QUORUM")] W4 --> BODY[("Distributed Blob Store<br/>────────────<br/>Key: email_id<br/>Compressed: zstd<br/>────────────<br/>3 replicas<br/>Encrypted: AES-256")] W5 --> S3[("S3 / GCS<br/>────────────<br/>Key: content_hash<br/>Dedup: SHA-256<br/>────────────<br/>11 nines durability<br/>Lifecycle tiering")] W6 --> ES[("Elasticsearch<br/>────────────<br/>Shard by: user_id range<br/>Fields: subject, body,<br/>from, to, labels<br/>────────────<br/>1 primary + 1 replica")] end subgraph "Read Path" R1["User reads inbox"] --> R2["Cache Check<br/>(Redis)"] R2 -->|"Hit"| R3["Return cached<br/>metadata"] R2 -->|"Miss"| META META --> R3 R4["User reads email"] --> BODY R5["User downloads file"] --> CDN["CDN<br/>(CloudFront)"] CDN --> S3 R6["User searches"] --> ES end style META fill:#4CAF50,stroke:#333,color:#fff style BODY fill:#2196F3,stroke:#333,color:#fff style S3 fill:#FF9800,stroke:#333,color:#fff style ES fill:#9C27B0,stroke:#333,color:#fff style R2 fill:#f44336,stroke:#333,color:#fff
Search Architecture (Detailed)
flowchart TB subgraph "Indexing Pipeline (Write)" direction TB NE["New Email"] --> KQ["Kafka Index Queue"] KQ --> IDX["Indexer Workers<br/>(Stateless, auto-scaled)"] IDX --> EXTRACT["Text Extraction:<br/>1. Strip HTML tags<br/>2. Decode MIME<br/>3. Extract attachment names<br/>4. Normalize Unicode"] EXTRACT --> ANALYZE["Analysis Pipeline:<br/>1. Tokenize<br/>2. Lowercase<br/>3. Remove stop words<br/>4. Stem (porter)"] ANALYZE --> ES_WRITE["Elasticsearch<br/>Index Write"] end subgraph "Query Pipeline (Read)" direction TB USER["User: search 'invoice 2025'"] --> API["API Server"] API --> QP["Query Parser:<br/>1. Extract operators<br/> (from:, to:, has:attachment)<br/>2. Date range extraction<br/>3. Build Elasticsearch DSL"] QP --> ES_READ["Elasticsearch<br/>Query Execution"] ES_READ --> RANK["Ranking:<br/>1. BM25 text relevance<br/>2. Recency boost<br/>3. User interaction signals"] RANK --> ENRICH["Enrich Results:<br/>Fetch metadata from<br/>Cassandra for display"] ENRICH --> RESULT["Return:<br/>email_id, subject, snippet,<br/>date, highlighted matches"] end ES_WRITE --> ES_CLUSTER[("Elasticsearch Cluster<br/>────────────<br/>20,000+ nodes<br/>Sharded by user_id range<br/>1 primary + 1 replica")] ES_READ --> ES_CLUSTER style KQ fill:#FF9800,stroke:#333,color:#fff style ES_CLUSTER fill:#9C27B0,stroke:#333,color:#fff style QP fill:#2196F3,stroke:#333,color:#fff
Aha Moments & Pitfalls — Bài học quan trọng
Aha Moments (Khoảnh khắc “à ha!“)
1. Email là async by nature — Queue-based architecture là tự nhiên
Aha: Khác với chat (cần real-time), email vốn dĩ là async. Sender gửi xong không kỳ vọng receiver đọc ngay. Điều này có nghĩa:
- Message queue (Kafka) là backbone tự nhiên — không phải workaround
- “Email gửi rồi nhưng chưa đến” là expected behavior (RFC cho phép retry đến 72 giờ)
- Retry strategy cực kỳ quan trọng — exponential backoff, bounce handling
2. Spam chiếm 80%+ traffic — Filter trước, process sau
Aha: Nếu hệ thống process tất cả incoming email trước khi filter spam, sẽ cần 5x resources. Architecture phải filter spam ở tầng sớm nhất (connection-level, trước cả khi accept DATA). Đây là lý do có multi-layer filtering: mỗi layer rẻ hơn (CPU cost) sẽ filter trước, layer đắt (ML model) filter sau.
3. Search at scale là bài toán riêng — Không thể dùng database query
Aha: SQL
LIKE '%keyword%'trên 10 trillion emails sẽ mất hàng giờ. Search engine (Elasticsearch/Lucene) với inverted index là hoàn toàn khác biệt:
- Build index lúc write → query nhanh lúc read
- Index là derived data — rebuild được từ primary store
- Shard by user_id → mỗi search chỉ hit 1-2 shards
4. Attachment storage dominates cost — 90% chi phí là file đính kèm
Aha: Metadata + email body chỉ chiếm ~15% storage. Attachments chiếm ~85%. Ba kỹ thuật tiết kiệm nhất:
- Deduplication: content-hash → lưu 1 lần dù gửi cho 1000 người
- Storage tiering: hot → warm → cold → archive (lifecycle policy)
- Compression: không compress file đã compressed (images, PDFs), compress text-based files
5. Email protocols cổ nhưng không thể thay thế
Aha: SMTP ra đời 1982, trước cả World Wide Web. Nhưng nó vẫn là backbone của email vì:
- Interoperability: Gmail có thể gửi cho Yahoo, Outlook, self-hosted server
- Decentralization: không có central authority, bất kỳ ai cũng có thể chạy email server
- Network effect: tỷ email addresses dùng SMTP → không thể migrate
- Modern email systems wrap SMTP (Gmail dùng REST API internal, nhưng vẫn speak SMTP ra bên ngoài)
6. “Delivered” không có nghĩa “inbox” — Email deliverability là nghệ thuật
Aha: Receiver’s SMTP server trả
250 OKchỉ có nghĩa “đã accepted vào queue”. Email có thể bị:
- Đưa vào spam folder (user không thấy)
- Silently discarded (sau spam filter)
- Delayed (queued for processing)
- Bounced sau khi accept (async bounce)
Đây là lý do email deliverability là một lĩnh vực riêng: IP warming, domain reputation, content optimization, feedback loops.
Pitfalls (Bẫy cần tránh)
1. Đừng dùng relational DB cho email storage
Sai: Dùng PostgreSQL/MySQL với
SELECT * FROM emails WHERE user_id = ? ORDER BY date DESC LIMIT 50. Đúng ở scale nhỏ (< 10M users), sai ở scale lớn (1B users). 10 trillion rows, sharding RDB phức tạp, join expensive. Dùng wide-column store (Cassandra/Bigtable) với partition key = user_id cho sequential reads.
2. Đừng scan virus sau khi lưu attachment
Sai: Upload attachment → lưu S3 → scan virus async. Đúng: Scan virus trước khi lưu. Nếu lưu trước, file infected có thể bị download trước khi scan xong.
3. Đừng ignore email headers khi implement threading
Sai: Group conversation chỉ dựa trên Subject line. Đúng: Dùng
Message-ID+In-Reply-To+Referencesheaders (RFC 2822). Subject chỉ là fallback. Nhiều email có cùng subject nhưng không liên quan.
4. Đừng search trực tiếp trên primary database
Sai: Full-text search trên Cassandra/Bigtable. Đúng: Cassandra không có full-text search capability. Dùng dedicated search engine (Elasticsearch). Index async, accept vài giây lag.
5. Đừng quên email size limits ở mọi layer
Sai: Chỉ validate attachment size ở API layer. Đúng: Validate ở mọi layer:
- SMTP server:
SIZEextension (RFC 1870) — reject oversize trước khi nhận DATA- API server: multipart upload size limit
- S3: object size limit
- Processing pipeline: timeout cho oversized emails
6. Đừng gửi email không có SPF/DKIM/DMARC
Sai: Setup SMTP server và gửi email luôn. Đúng: Không có SPF/DKIM/DMARC → emails sẽ vào spam folder của mọi receiver. Setup cả 3 là prerequisite để email deliverability hoạt động.
7. Đừng underestimate spam filtering resources
Sai: “Spam filter chỉ là 1 service nhỏ.” Đúng: Spam filter phải process 100% incoming traffic (bao gồm 80% spam) → nó cần nhiều resources hơn cả email delivery pipeline. ML inference cho mỗi email, URL scanning, attachment scanning — tất cả phải xong trong vài giây.
Interview Tips — Khi gặp bài này trong phỏng vấn
Câu hỏi thường gặp từ interviewer
| Câu hỏi | Hướng trả lời |
|---|---|
| ”Tại sao không dùng MySQL cho email storage?” | Giải thích write-heavy workload, partition key pattern, horizontal scaling của Cassandra |
| ”Làm sao handle 80% spam traffic?” | Multi-layer filtering, reject sớm nhất có thể ở connection-level |
| ”Search latency target bao nhiêu?” | < 500ms cho full-text, < 100ms cho autocomplete. Dùng Elasticsearch, shard by user_id |
| ”Attachment storage cost quá lớn, optimize thế nào?” | Deduplication (SHA-256), storage tiering (hot/warm/cold/archive), CDN cho download |
| ”Làm sao đảm bảo email không bị mất?” | Durable queue (Kafka với replication), multi-datacenter storage replication, 11 nines S3 durability |
| ”Threading hoạt động thế nào?” | Message-ID + In-Reply-To + References headers (RFC 2822), fallback subject matching |
| ”End-to-end encryption trade-offs?” | E2EE → server cannot search, cannot spam filter → client-side only. Trade security vs functionality |
Framework khi trả lời
- Start with protocols: Show bạn hiểu SMTP, IMAP, POP3 — đây là foundation
- Separate 3 flows: Sending, Receiving, Reading — mỗi flow có architecture khác
- Emphasize spam early: 80% traffic là spam — nếu không mention → red flag
- Storage separation: Metadata vs Body vs Attachments — 3 loại data khác nhau, 3 storage engines
- Search as separate system: Elasticsearch/Lucene, async indexing, derived data
- Security is not optional: SPF/DKIM/DMARC, TLS, virus scanning — mention ngay từ đầu
Tổng kết
| Aspect | Key Decision | Lý do |
|---|---|---|
| Protocol | SMTP (send/receive) + REST API (read) + IMAP (backward compat) | SMTP là Internet standard, API flexible hơn cho modern clients |
| Metadata Storage | Cassandra/Bigtable | Write-heavy, partition by user_id, horizontal scaling |
| Body Storage | Distributed Blob Store | Compress với zstd, separate từ metadata cho independent scaling |
| Attachment Storage | S3/GCS với dedup + tiering | 90% of storage cost, dedup + lifecycle policy tiết kiệm lớn |
| Search | Elasticsearch/Lucene | Full-text search, inverted index, shard by user_id |
| Spam Filter | Multi-layer: connection → envelope → header → content (ML) | Filter sớm = tiết kiệm resources, ML cho accuracy |
| Queue | Kafka | Decouple sending/receiving/processing, handle burst, retry |
| Cache | Redis | Inbox metadata cache, unread counts, user preferences |
| Security | SPF/DKIM/DMARC + TLS + E2EE optional | Email authentication, encryption in transit + at rest |
| Sync | IMAP IDLE (desktop) + Delta sync (mobile/web) | Push for desktop, efficient sync for mobile |
“Email system là bài test tổng hợp: distributed storage, search engine, security, queue processing, protocol design — tất cả trong một. Nếu em design được email system tốt, em design được hầu hết mọi thứ.”
Liên kết nội bộ:
- Tuan-08-Message-Queue — Kafka architecture, queue patterns (dùng cho email queues)
- Tuan-15-Data-Security-Encryption — Encryption strategies, TLS, key management
- Tuan-14-AuthN-AuthZ-Security — Authentication patterns, OAuth2 (dùng cho email client auth)
- Tuan-19-Design-Notification-System — Push notification architecture (email + push notification overlap)
- Tuan-17-Design-Chat-System — Messaging patterns comparison (real-time vs async)
- Tuan-13-Monitoring-Observability — Monitoring, alerting, dashboards