Get Hedhog updates in your inbox
New releases, fresh recipes, and breaking changes — no spam.
inbox Submodule
threads
Folder-based thread inbox (inbox, outbox, sent, archived, trash, starred) built with raw SQL aggregation over inbox_message, plus archive/trash/star/read state and permanent deletion.
Source path: libraries/inbox/src/thread
Module file: thread.module.ts
Introduction
The Threads submodule is the read model for the mailbox UI: `inbox_thread` groups related `inbox_message` rows and exposes them through six virtual folders — `inbox`, `outbox`, `sent`, `archived`, `trash`, `starred` — computed by `ThreadService.resolveFolder` from a combination of the thread's own `status` (`open` | `archived` | `trash`), its `starred` flag, and the direction/status of its most recent message. A thread is never directly assigned to a folder column; `outbox` specifically means "open, not starred, and the latest message is an outbound message still `queued`" — the moment that message is sent or fails, the same thread reappears under `sent` without any explicit move operation.
`GET /inbox/threads` is implemented as a single hand-written SQL query (via `Prisma.sql` / `$queryRaw`) rather than the Prisma query builder, because folder membership, label aggregation (`json_agg` of distinct labels across all of a thread's messages), unread detection (`EXISTS` over `inbox_message`), and per-thread message counts all need to be computed relative to the *accessible* account set for the calling user in one pass. Every list and detail call is scoped through `InboxAccessService.listAccessibleAccountIds` / `assertThreadAccess`, so a user only ever sees threads that have at least one message on an account they own or are a member of (or, for admin/admin-inbox, every account).
Removing a thread has two distinct operations with different destructiveness. `PATCH /inbox/threads/:id/trash` is a soft move: it sets `inbox_thread.status = 'trash'`, but first finds any still-`queued` outbound messages on that thread, cancels their pending/scheduled/retrying queue jobs via `QueueJobService.cancel`, flips those messages to `status: failed`, and writes a `mail_event` of type `failed` explaining the message was removed from the outbox by the user — this prevents a queued send from silently going out after the user thought they deleted it. `DELETE /inbox/threads/:id/permanent` and `DELETE /inbox/threads/trash/empty` are hard deletes: inside a transaction they remove every `mail_event` and `queue_job` row tied to the thread's messages, delete the messages themselves, and finally delete the thread row — data that cannot be recovered afterward, unlike the `trash` status which is just a filter.
`PATCH /inbox/threads/:id/star` and `/unstar` update the `starred` boolean via a raw `$executeRaw UPDATE` rather than the Prisma client, and `/read` / `/unread` bulk-update the `unread` flag across every non-deleted message in the thread scoped to the caller's accessible accounts (not just the latest message), so partially-read threads shared across multiple mailbox members are marked consistently for the accounts the caller can see.
HTTP Endpoints
10 endpoints
Paginated list of threads in a folder, scoped to the caller's accessible accounts. Each row includes the latest message preview, unread flag, star state, message count, and aggregated labels across the thread.
- Query
- page, pageSize, search, accountId?, folder? (inbox | outbox | sent | archived | trash | starred, default inbox), unreadOnly?
Get full thread detail including every message (with recipients and the latest mail_event) ordered chronologically, plus aggregated labels. Includes soft-deleted messages when the thread itself is in the trash folder.
- Params
- id (int)
Move a thread to the archived folder (status: archived).
- Params
- id (int)
Move a thread to the trash folder. Cancels any still-queued outbound messages on the thread (queue job + status: failed + a failed mail_event) so a pending send is not delivered after the user trashed it.
- Params
- id (int)
Permanently delete a thread and all of its messages, mail_events, and queue_job rows. Irreversible.
- Params
- id (int)
Permanently delete every thread currently in the trash folder across all accounts accessible to the caller.
Mark every non-deleted message in the thread (within the caller's accessible accounts) as read.
- Params
- id (int)
Mark every non-deleted message in the thread (within the caller's accessible accounts) as unread.
- Params
- id (int)
Star a thread (moves it into the starred folder while status remains open).
- Params
- id (int)
Remove the star from a thread.
- Params
- id (int)