Logotipo Hedhog

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)