Part VI β The Blueprint Β· Lesson 11.8
HLD β Email Client
In one lineAn offline-first local database that syncs both ways β the design is the sync protocol.
Where you've seen itTriaging your inbox offline, then watching every action apply correctly once you reconnect.
Diagram
1β2 Β· SCOPE AND SCALE
in scope list Β· read Β· compose/send Β· search Β· labels Β· offline
out calendar Β· contacts Β· spam ML Β· attachments storage
scale 50k messages per mailbox Β· offline is a core requirement
multi-device: same account open in three places3β4 Β· LOCAL-FIRST ARCHITECTURE
ββββββββββββββββββββββββββββββββββββββββββββββββ
β UI reads ONLY from IndexedDB (6.5) β β instant, always
ββββββββββββββββββββββββββββββββββββββββββββββββ€
β messages keyPath id Β· index by-thread, β
β by-date, by-label β
β threads id, messageIds[], lastMessageAt β
β outbox queued sends + pending actions β
β meta syncWatermark, deviceId β
βββββββββββββββββββββ¬βββββββββββββββββββββββββββ
β sync engine (service worker, 9.1)
βΌ
ββββββββββββββββ
β Mail API β
ββββββββββββββββ
The UI never waits on the network. Ever.6a Β· DEEP DIVE β TWO-WAY SYNC
PULL PUSH
GET /changes?since=<watermark> replay outbox in order
β β
βΌ βΌ
[{id, op:'update', labels:[...]}, POST /messages/9/archive
{id, op:'delete'}, ...] β
β ββββββ΄ββββββ
βΌ ack conflict
apply to IndexedDB β β
watermark = response.cursor remove from resolve (below)
outbox
Watermark = server-issued cursor, NOT a client clock β
device clocks are wrong and unsynchronised.6b Β· DEEP DIVE β CONFLICT RESOLUTION
conflict type resolution
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
label add/remove (commutative) merge β apply both sides
read/unread flag last-write-wins by server time
delete vs edit delete wins, warn the user
draft edited on two devices keep both, surface "2 versions"
β never silently discard user text
Rule: commutative ops merge; destructive ops need an explicit rule;
user-authored content is never auto-discarded.How it works
- Read path is local only β the UI queries IndexedDB, so the inbox opens instantly and works offline.
- Write path is optimistic: apply locally, enqueue in the outbox, replay on reconnect (9.1).
- Sync pulls a delta since the server watermark; full re-sync only on first login or corruption.
- Search runs locally over an IndexedDB index for cached mail, falling back to server search for older messages.
- Virtualize the message list (10.7) β 50,000 rows will not live in the DOM.
Trade-offs
| Choice | Cost |
|---|---|
| Local-first reads | Storage quota, eviction handling, migrations |
| Optimistic writes | Conflict resolution UI you must design |
| Local search index | Build cost and staleness |
| Full sync on login | Slow first run; simple correctness |
Interview angle
"The user archives 20 emails offline. What happens?"
- Each action applies to IndexedDB immediately and appends to an ordered outbox; the UI reflects it at once.
- On reconnect the outbox replays in order with idempotency keys, so retries can't double-apply.
- Conflicts resolve by type β label changes merge, deletes win over edits, and conflicting drafts are kept as two versions rather than silently dropped.
Recap
- The UI reads only from the local database; the network is a background concern.
- Sync is a server-issued watermark plus an ordered outbox, never a client clock.
- Classify conflicts: merge commutative ops, define rules for destructive ones, never discard user text.