Addresses, folders, and threading
Addresses
addresses is a per-user table that maps mail addresses Lumen will accept or send from. Each user can have many addresses. Exactly one is marked as primary; deleting the primary promotes the next-oldest address.
The InitPage doesn't create any addresses — every user adds their own from Settings → Addresses after first sign-in.
Folders
Every user has the following system folders (always present, never deletable):
inbox— incoming mailsent— sent maildrafts— autosaved draftsstarred— virtual: messages withis_starred = 1, excluding trash & spamarchive— manual archivespam— manual spamtrash— soft-delete bucket; emptied on demand
Custom folders (rows in the folders table) are addressed by their folder.id UUID rather than by name. Deleting a custom folder moves its messages back to inbox.
The "Move to" menu writes the chosen folder id directly to messages.folder.
Threading
Lumen builds a synthetic thread_id on inbound mail:
- If the message has an
In-Reply-Toheader, that's the thread. - Otherwise the first
Referencestoken is used. - Otherwise the message's own
Message-IDbecomes its thread root.
Outgoing replies inherit the parent's thread_id, so a reply chain stays together in the database. The frontend currently lists messages per folder and shows them flat; GET /api/threads/:threadId returns all messages in a thread for a future thread view.
Search
/api/messages?folder=…&q=… runs a LIKE scan against subject, preview, from_address, and to_addresses. It's not a full-text index — for larger mailboxes consider porting this to D1 FTS5 or to a separate index Worker.
Trash semantics
DELETE /api/messages/:id:
- If the message is not in trash, it's moved to
trash(soft delete). - If it's already in trash, the row is deleted, attachments are removed from R2, and the raw
.emlblob is removed.
POST /api/messages/trash/empty does a bulk hard-delete for the current user.