Date: 2026-06-05
Time: 13:47
This file is the core implementation of a distributed email service — one of the system design interview reference implementations in the sdi-implementations repo. It provides an in-memory simulation of an email platform (think Gmail/Outlook backend) covering account management, sending/receiving, folder organization, threading, drafts, search, and read tracking.
It owns all email lifecycle state: creation, delivery, storage, retrieval, organization, and deletion. There is no persistence layer — everything lives in Python dicts and sets, which is intentional for an SDI teaching implementation that focuses on API design and data modeling rather than storage mechanics.
Email (data class)A plain value object representing an email to be sent. Fields map directly to RFC 5322 concepts: fromaddr, to, subject, body, cc, bcc, attachments, inreplyto. Mutable defaults are handled correctly with or [] in init_.
Note: Email is the input representation. Once sent, emails are stored as plain dicts (not Email instances) in self.emails.
EmailService (service class)The main service with seven internal stores:
| Store | Type | Purpose |
|-------|------|---------|
| accounts | {email: name} | User registry |
| emails | {msg_id: dict} | Canonical email store |
| userfolders | {user: {folder: [msgids]}} | Per-user folder organization |
| readstatus | {user: set(msgids)} | Tracks which messages a user has read |
| threads | {threadid: [msgids]} | Conversation threading |
| msgtothread | {msgid: threadid} | Reverse index for thread lookup |
| drafts | {draft_id: Email} | Draft email objects (pre-send) |
Key methods and their contracts:
send(email) -> msg_id: The main write path. Generates a UUID, stores the email dict, assigns a thread, delivers to sender's sent folder (auto-marked read), and delivers to all recipients' inboxes. Returns the message ID.savedraft / updatedraft / senddraft: Draft lifecycle. Drafts are stored both as Email objects (in self.drafts) and as email dicts (in self.emails with isdraft: True). send_draft promotes a draft to a real email by popping it from both stores and calling send().listfolder(user, folder, limit, cursor) -> {emails, nextcursor, total}: Paginated folder listing in reverse chronological order. Uses integer-based cursor pagination.getemail(user, messageid): Retrieves an email and side-effects a mark-read. This is the "open email" action.search(user, query, ...): Linear scan with text matching on subject/body plus optional filters (from, attachments, read status). No indexing.delete(user, message_id): Two-phase delete — first call moves to trash, second call from trash permanently removes.Lazy user initialization: inituser() is called at the start of most operations to ensure default folders exist. This means accounts don't strictly need create_account() — any operation will bootstrap the user. This is a defensive pattern common in SDI implementations where you don't want operations to fail just because setup was skipped.
Separate input/storage representations: Email objects go in, plain dicts come out. The storeemail method handles the conversion. This decouples the send API from the storage/query format.
Thread assignment by reply chain: assignthread uses inreplyto to find an existing thread. If the replied-to message has a thread, the new message joins it. Otherwise, a new thread is created with threadid = msgid (the first message's ID becomes the thread ID). This is a simplified version of how Gmail threading works.
Cursor-based pagination: list_folder uses integer offsets serialized as strings. This is a teaching simplification — real systems use opaque cursors to avoid issues with concurrent inserts shifting offsets.
Minimal — only stdlib:
uuid for message/draft ID generationdatetime for UTC timestampsImported by two test files (testemail.py, testemail_service.py), meaning this module is tested from two angles, likely unit vs. integration or different test authors.
Send path: send() → storeemail() (Email → dict) → assignthread() (thread management) → addto_folder() for sender's "sent" + each recipient's "inbox". BCC recipients get the message in their inbox but BCC addresses are stored in the email dict — a real system would strip them from the stored record for non-sender views.
Draft path: savedraft() stores both the Email object and a dict representation → updatedraft() mutates both in place → send_draft() pops from draft stores, removes from drafts folder, calls send() for normal delivery.
Read path: listfolder() returns paginated email dicts. getemail() returns a single email and marks it read. get_thread() returns all emails in a thread.
Delete path: delete() checks if message is already in trash. If yes, removes permanently. If no, calls movetofolder(user, message_id, "trash").
1. Every sent message gets a UUID and UTC timestamp — send() always generates both before any storage.
2. Sender's copy is auto-marked read — in send(), the sender's message ID is added to their read_status immediately.
3. Thread ID equals the first message's ID — when no inreplyto exists or the replied-to message has no thread, threadid = msgid.
4. movetofolder removes from exactly one source folder — it iterates all folders and breaks on the first match, so a message can only exist in one folder per user.
5. Two-phase delete — first delete moves to trash, second delete from trash permanently removes. There's no "empty trash" bulk operation.
6. get_email always marks read — there's no way to retrieve an email without the read side-effect.
Minimal and deliberate:
updatedraft and senddraft raise ValueError("Draft not found") if the draft ID doesn't exist.get_email returns None for unknown message IDs rather than raising.addto_folder lazily creates folders, search skips missing message IDs with if not e: continue).distributed-email-service/testemailservice.py — See how the service API is exercised end-to-end, especially threading and draft workflowsdistributed-email-service/email_service.py:search — Linear scan search is the biggest scalability gap; worth exploring how a real system would use inverted indexesbcc-privacy-leak — The current implementation stores BCC addresses in the email dict visible to all recipients, which violates BCC semantics in a real systemdistributed-email-service/plan.md — Design decisions and trade-offs considered before implementationcursor-pagination-consistency — Integer-offset cursors break under concurrent writes; worth comparing to token-based cursors used by Gmail APIemail-service-single-folder-per-user — A message can only exist in one folder per user; movetofolder removes from the source before adding to the targetemail-service-thread-id-is-first-msg — Thread IDs are the message ID of the thread's first message, not a separately generated identifieremail-service-get-email-marks-read — getemail always side-effects a markread; there is no read-without-marking retrieval pathemail-service-two-phase-delete — First delete() call moves to trash; second call on a trashed message permanently removes itemail-service-bcc-stored-in-record — BCC recipients are stored in the email dict alongside to/cc, which would leak BCC information to other recipients in a real system