---
title: "Threads"
description: "Every operation in this group: what it accepts, what it returns and the errors it can answer with."
url: "https://nextmail.sh/docs/api/reference/threads"
area: "API"
category: "Reference"
---

# Threads

Every operation in this group: what it accepts, what it returns and the errors it can answer with.

## Operations

### `GET /threads`

List threads in a folder

Reads the local index. Passing `query` searches that same index.

Plain words must all appear, and each one matches loosely: case, accents and separators are ignored and part of a longer word counts, so `min` and `ben jamin` both find "Benjamin". A quoted phrase is matched as written apart from case and accents, so its separators have to line up: `"ben jamin"` does not find "Ben-Jamin", while `"quarterly invoice"` finds "Quarterly invoice". When nothing matches exactly, close spellings are returned instead, so `benjimin` finds "Benjamin": a plain word, or the value of `from:`, `to:`, `cc:`, `subject:`, `body:`, `filename:` or `label:`, may differ from the start of a word by one typo (a changed, missing, extra or swapped letter) when it has four to seven letters and by two when it has eight or more, while a quoted phrase, a word containing a digit, a shorter word and an excluded word still match exactly, and the pages that follow keep matching the same way. Filler words are dropped from a list of plain words when something else is left to search for, so `mail server` searches for `server` alone and `emails from john` searches for `john`. The filler is `a`, `an`, `the`, `and`, `or`, `of`, `to`, `from`, `for`, `about`, `with`, `in`, `on`, `at`, `by`, `my`, `me`, `all`, `any`, `some`, `show`, `find`, `get`, `email`, `emails`, `mail`, `mails`, `message`, `messages`, `thread` and `threads`, and it is kept when dropping it would leave nothing but a single letter.

Operators narrow the search: `from:`, `to:`, `cc:`, `subject:`, `body:`, `label:`, `filename:`, `in:` (`inbox`, `sent`, `drafts`, `spam`, `trash`, `archive`, `snoozed`, `anywhere` or a label name), `is:` (`unread`, `read`, `starred`, `important`, `snoozed`, `muted`, `draft`, `sent`, `bounced` for a delivery failure notice, `unlabelled` for a conversation that carries none of your labels), `has:` (`attachment`, `pdf`, `image`, `video`, `audio`, `document`, `spreadsheet`, `presentation`, `userlabels`, `nouserlabels`), `after:YYYY/MM/DD`, `before:YYYY/MM/DD`, `newer_than:7d` and `older_than:1y` (units `h`, `d`, `w`, `m`, `y`). Combine them with `OR`, `AND`, `NOT`, parentheses, braces for a set of alternatives (`{stripe paddle}`) and a leading `-` to exclude. Recipients are stored as one list without roles and never hold a Bcc, so `cc:` reads the same field as `to:` and `bcc:` matches nothing of its own. `from:me` is mail you sent, and `to:me` is mail carrying one of your own addresses, aliases included, among its recipients or as the address it was delivered to.

A value the search cannot use is ignored rather than narrowing, so a typo in a value widens the result instead of emptying it. Ignored: `category:`, `larger:`, `smaller:`, `size:`, `messagesize:`, `list:`, `rfc822msgid:`, `received:`, `sent:`, the category words (`is:primary`, `is:personal`, `is:social`, `is:promotions`, `is:updates`, `is:forums`, `is:reservations`, `is:purchases`), a `has:` word that names none of the kinds above, an `importance:` other than `high` or `low`, a date that cannot be read, and a duration whose unit is not `h`, `d`, `w`, `m` or `y`. An operator name it does not know, `project:` for instance, is searched as plain text instead.

Words and the `from:`, `to:`, `cc:`, `subject:` and `body:` operators read only the newest message on each thread: its sender, its recipients, its subject and the first 4,000 characters of its body. `filename:` and `has:` read every attachment on the whole conversation, and `label:`, `in:` and `is:` read the whole conversation. A plain word also matches the name of any attachment on the conversation, whichever message carried it. A message that arrived encrypted has no body text to match. `folder` still applies unless the query names one with `in:`, or with an `is:` that is a folder such as `is:sent`, and `in:anywhere` searches every folder, on its own as well as beside other terms. A drafts listing is the exception: `GET /drafts` and `GET /threads?folder=draft` stay in drafts whatever the query names.

Dates read the newest activity on the thread, in UTC. `after:` includes the day it names and `before:` excludes it. A date can be written `YYYY/MM/DD`, `YYYY-MM-DD`, `YYYYMMDD`, as a bare year, or as epoch seconds or milliseconds; A short date reads day first (`16/09/2026`), unless the second number cannot be a month (`09/16/2026`), and a number above 12 settles it either way. A day that does not exist is rejected and ignored.

Send `semantic=true` to match the plain words of `query` by meaning rather than by spelling. Describe the mail in a few words, such as `flight to Berlin` or `invoices I still owe`, in any language, and the threads closest in meaning come back best match first, together with every thread the words match as text, which ranks above a thread that only means the same. A thread is kept only when it is clearly closer to the words than the rest of the mailbox, so a description nothing fits returns only the text matches. Operators narrow the search exactly as above and `folder` and the other filters still apply, while `sort` does not. Meaning is read from the whole conversation, newest messages first and up to 12,000 characters, so an earlier message counts too. A conversation with an encrypted message is never read for meaning and is found by its text alone, new mail can be found by meaning about a minute after it arrives, and a workspace that turned search by meaning off in its settings (`semanticSearch: false`) gets a text search whatever `semantic` says. A query with no plain words, `from:ada` on its own for instance, is a text search whatever `semantic` says.

`nextPageToken` is opaque. Pass back what you were given, never construct one.

Requires the `threads:read` scope.

- Scopes: `threads:read`.

**Query parameters**

- `folder` (`string`, default `"inbox"`): Label the threads must carry, case insensitive. Defaults to `inbox`, and `bin` is read as `trash`.
- `query` (`string`): Mailbox search. Plain words must all appear and match loosely, ignoring case, accents and separators, against the newest message on each thread and the name of any attachment on it, while a quoted phrase has to appear as written and filler words such as `the` or `emails` are dropped when something else is left to search for. Operators such as `from:ada`, `label:Invoices`, `is:unread`, `has:pdf` and `newer_than:7d` narrow it, and naming a folder with `in:` replaces `folder`, so `in:anywhere` searches them all. When nothing matches exactly, close spellings are returned instead, so `benjimin` finds "Benjamin". A value the search cannot use is ignored rather than narrowing.
- `labelIds` (`string`): Comma-separated label ids. A thread must carry every one of them and sit in `folder`, which is how the app filters a folder by a label. To list a label's threads whichever folder they are in, pass its id as `folder` instead: `folder=USER_BIG_CLIENTS`.
- `sort` (`string`, one of `"newest"`, `"oldest"`, `"sender"`, `"subject"`, default `"newest"`): The four orders of the thread list: `newest`, `oldest`, `sender` and `subject`. `sender` and `subject` are alphabetical, newest first within one sender or subject. Threads that arrived in the same instant are ordered by id, so every order pages to the end without skipping or repeating one.
- `dateFrom` (`string`, format `date-time`): Keeps threads whose newest message arrived at or after this instant. ISO 8601 with a time and an offset, such as `2026-09-01T00:00:00Z`.
- `dateTo` (`string`, format `date-time`): Keeps threads whose newest message arrived at or before this instant. Both ends are included, and `dateFrom` after `dateTo` is a 422.
- `fromContacts` (`string`, one of `"true"`, `"false"`): `true` keeps only threads whose newest message came from a saved contact, the From contacts filter of the thread list. A key limited to particular addresses reads the contacts its owner saved, and an app acting for a member reads the contacts that member saved.
- `semantic` (`string`, one of `"true"`, `"false"`): `true` matches the plain words of `query` by meaning instead of spelling, in any language, and returns the closest threads best match first along with every thread the words match as text. Operators and the other filters still apply and `sort` does not. It changes nothing when `query` has no plain words or when the workspace has turned search by meaning off.
- `category` (`string`, one of `"primary"`, `"promotions"`, `"updates"`, `"social"`, `"forums"`): Keep only the threads sorted into this inbox tab: `primary`, `promotions`, `updates`, `social` or `forums`. `primary` is every thread in no other tab. In `query`, `category:promotions` filters the same way in any folder.
- `address` (`string`, 3 to 320 characters): Keep only the threads delivered to this address, the address filter of the thread list. One the key does not reach lists nothing rather than failing.
- `limit` (`integer`, at least 1, at most 100): Threads per page, a whole number from 1 to 100. Defaults to 25.
- `pageToken` (`string`): The previous page's `nextPageToken`, passed back as it came. Send the same `sort`, `semantic`, dates and filters with it.

**Returns**

- `200` `ThreadList`: A page of threads.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.threads.list()`](https://nextmail.sh/docs/sdk/reference/threads#list), [`nextmail.threads.listAll()`](https://nextmail.sh/docs/sdk/reference/threads#listAll), [`nextmail.threads.iterate()`](https://nextmail.sh/docs/sdk/reference/threads#iterate); Python [`threads.list()`](https://nextmail.sh/docs/python/reference/threads#list), [`threads.list_all()`](https://nextmail.sh/docs/python/reference/threads#listAll), [`threads.iterate()`](https://nextmail.sh/docs/python/reference/threads#iterate); Ruby [`threads.list`](https://nextmail.sh/docs/ruby/reference/threads#list), [`threads.list_all`](https://nextmail.sh/docs/ruby/reference/threads#listAll), [`threads.iterate`](https://nextmail.sh/docs/ruby/reference/threads#iterate); PHP [`threads->list`](https://nextmail.sh/docs/php/reference/threads#list), [`threads->listAll`](https://nextmail.sh/docs/php/reference/threads#listAll), [`threads->iterate`](https://nextmail.sh/docs/php/reference/threads#iterate); Go [`Threads.List`](https://nextmail.sh/docs/go/reference/threads#list), [`Threads.ListAll`](https://nextmail.sh/docs/go/reference/threads#listAll), [`Threads.Iterate`](https://nextmail.sh/docs/go/reference/threads#iterate); Java [`threads().list`](https://nextmail.sh/docs/java/reference/threads#list), [`threads().listAll`](https://nextmail.sh/docs/java/reference/threads#listAll), [`threads().iterate`](https://nextmail.sh/docs/java/reference/threads#iterate); C# [`Threads.ListAsync`](https://nextmail.sh/docs/csharp/reference/threads#list), [`Threads.ListAllAsync`](https://nextmail.sh/docs/csharp/reference/threads#listAll), [`Threads.IterateAsync`](https://nextmail.sh/docs/csharp/reference/threads#iterate); CLI [`nextmail threads list`](https://nextmail.sh/docs/cli/reference/threads#threads-list); MCP [`listThreads`](https://nextmail.sh/docs/mcp/tools/reading#listThreads).

### `GET /threads/{id}`

Retrieve a thread

Every message in it, not only the most recent.

The messages are passed through as the mailbox stored them rather than projected onto a field list, and this document describes only `encryption`, which is the one field whose absence a caller cannot safely guess at. A message that arrived encrypted carries it and has an EMPTY BODY; read `encryption.format` before concluding a message was empty, and note that the two `*-signed` formats are ordinary readable mail.

Requires the `threads:read` scope.

- Scopes: `threads:read`.

**Path parameters**

- `id` (`string`, required): Thread id, as returned by `list` or carried on a message.

**Returns**

- `200` `Thread`: The thread, with every message on it.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.threads.get()`](https://nextmail.sh/docs/sdk/reference/threads#get); Python [`threads.get()`](https://nextmail.sh/docs/python/reference/threads#get); Ruby [`threads.get`](https://nextmail.sh/docs/ruby/reference/threads#get); PHP [`threads->get`](https://nextmail.sh/docs/php/reference/threads#get); Go [`Threads.Get`](https://nextmail.sh/docs/go/reference/threads#get); Java [`threads().get`](https://nextmail.sh/docs/java/reference/threads#get); C# [`Threads.GetAsync`](https://nextmail.sh/docs/csharp/reference/threads#get); CLI [`nextmail threads get`](https://nextmail.sh/docs/cli/reference/threads#threads-get); MCP [`getThread`](https://nextmail.sh/docs/mcp/tools/reading#getThread).

### `PATCH /threads/{id}`

Mark read/unread and change labels

Applies and removes labels, and sets read state, on one conversation. Label ids come from `GET /labels`; the system ids `INBOX`, `ARCHIVE`, `STARRED`, `IMPORTANT`, `SPAM` and `UNREAD` are taken in any case, so archiving is `addLabelIds: ["ARCHIVE"]` with `removeLabelIds: ["INBOX"]`. `MUTE` in `addLabelIds` mutes the thread as `POST /threads/{id}/mute` does, and the tab labels `CATEGORY_PROMOTIONS`, `CATEGORY_UPDATES`, `CATEGORY_SOCIAL` and `CATEGORY_FORUMS` move a thread between inbox tabs. An id in `addLabelIds` that names no label is a 422 `label_not_found`, and nothing on the thread changes: create the label with `POST /labels` first. An unknown id in `removeLabelIds` is not an error, since the thread does not carry it. `TRASH`, `SNOOZED` and `DRAFT` are refused with `label_not_directly_settable`; use the trash and snooze endpoints.

Requires the `threads:write` scope.

- Scopes: `threads:write`.

**Path parameters**

- `id` (`string`, required): Thread id.

**Request body**

- `read` (`boolean`): `true` removes `UNREAD` and `false` adds it.
- `addLabelIds` (`string[]`, up to 50 items): Label ids to put on the thread. Each must name a label.
- `removeLabelIds` (`string[]`, up to 50 items): Label ids to take off the thread.

**Returns**

- `200` `ThreadLabelChange`: Applied.

**Errors**

- `422`: `label_not_found` for an id in `addLabelIds` that names no label, or `label_not_directly_settable` for `TRASH`, `SNOOZED` or `DRAFT`.
- The errors every operation can return: `400`, `401`, `403`, `404`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.threads.update()`](https://nextmail.sh/docs/sdk/reference/threads#update); Python [`threads.update()`](https://nextmail.sh/docs/python/reference/threads#update); Ruby [`threads.update`](https://nextmail.sh/docs/ruby/reference/threads#update); PHP [`threads->update`](https://nextmail.sh/docs/php/reference/threads#update); Go [`Threads.Update`](https://nextmail.sh/docs/go/reference/threads#update); Java [`threads().update`](https://nextmail.sh/docs/java/reference/threads#update); C# [`Threads.UpdateAsync`](https://nextmail.sh/docs/csharp/reference/threads#update); CLI [`nextmail threads update`](https://nextmail.sh/docs/cli/reference/threads#threads-update); MCP [`markThreadsRead`](https://nextmail.sh/docs/mcp/tools/organising#markThreadsRead), [`markThreadsUnread`](https://nextmail.sh/docs/mcp/tools/organising#markThreadsUnread), [`modifyLabels`](https://nextmail.sh/docs/mcp/tools/organising#modifyLabels), [`restoreThreads`](https://nextmail.sh/docs/mcp/tools/organising#restoreThreads).

### `DELETE /threads/{id}`

Delete a thread for good

Deletes every message in the thread, with its attachments, and cannot be undone, which is what Delete from Bin does in the app. It works on a thread in any folder, so move it to the Bin with `POST /threads/{id}/trash` when you only mean to throw it away.

Requires the `threads:write` scope.

- Scopes: `threads:write`.

**Path parameters**

- `id` (`string`, required): The thread, as `GET /threads` returns it.

**Returns**

- `200` `DeletedThread`: Gone for good.

**Errors**

- `500`: `thread_delete_failed`: the thread could not be deleted, and nothing was removed.
- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.threads.delete()`](https://nextmail.sh/docs/sdk/reference/threads#delete); Python [`threads.delete()`](https://nextmail.sh/docs/python/reference/threads#delete); Ruby [`threads.delete`](https://nextmail.sh/docs/ruby/reference/threads#delete); PHP [`threads->delete`](https://nextmail.sh/docs/php/reference/threads#delete); Go [`Threads.Delete`](https://nextmail.sh/docs/go/reference/threads#delete); Java [`threads().delete`](https://nextmail.sh/docs/java/reference/threads#delete); C# [`Threads.DeleteAsync`](https://nextmail.sh/docs/csharp/reference/threads#delete); CLI [`nextmail threads delete`](https://nextmail.sh/docs/cli/reference/threads#threads-delete); MCP [`deleteThreadsForever`](https://nextmail.sh/docs/mcp/tools/organising#deleteThreadsForever).

### `POST /threads/{id}/trash`

Move a thread to the Bin

Clears INBOX, SPAM, SNOOZED and ARCHIVE together, which is what the app does. Adding the `TRASH` label by hand through PATCH is refused, because doing only half of it leaves the thread listed in the Bin AND its old folder.

Requires the `threads:write` scope.

- Scopes: `threads:write`.

**Path parameters**

- `id` (`string`, required): Thread id.

**Returns**

- `200` `TrashedThread`: Trashed.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.threads.trash()`](https://nextmail.sh/docs/sdk/reference/threads#trash); Python [`threads.trash()`](https://nextmail.sh/docs/python/reference/threads#trash); Ruby [`threads.trash`](https://nextmail.sh/docs/ruby/reference/threads#trash); PHP [`threads->trash`](https://nextmail.sh/docs/php/reference/threads#trash); Go [`Threads.Trash`](https://nextmail.sh/docs/go/reference/threads#trash); Java [`threads().trash`](https://nextmail.sh/docs/java/reference/threads#trash); C# [`Threads.TrashAsync`](https://nextmail.sh/docs/csharp/reference/threads#trash); CLI [`nextmail threads trash`](https://nextmail.sh/docs/cli/reference/threads#threads-trash); MCP [`trashThreads`](https://nextmail.sh/docs/mcp/tools/organising#trashThreads).

### `POST /threads/{id}/snooze`

Snooze a thread

Hides it and schedules its return. Both halves happen together: the label hides it, a stored wake time brings it back, and a thread snoozed by label alone never returns.

Requires the `threads:write` scope.

- Scopes: `threads:write`.

**Path parameters**

- `id` (`string`, required): Thread id.

**Request body**

- `wakeAt` (`string`, required, format `date-time`)

**Returns**

- `200` `ThreadSnooze`: Snoozed.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.threads.snooze()`](https://nextmail.sh/docs/sdk/reference/threads#snooze); Python [`threads.snooze()`](https://nextmail.sh/docs/python/reference/threads#snooze); Ruby [`threads.snooze`](https://nextmail.sh/docs/ruby/reference/threads#snooze); PHP [`threads->snooze`](https://nextmail.sh/docs/php/reference/threads#snooze); Go [`Threads.Snooze`](https://nextmail.sh/docs/go/reference/threads#snooze); Java [`threads().snooze`](https://nextmail.sh/docs/java/reference/threads#snooze); C# [`Threads.SnoozeAsync`](https://nextmail.sh/docs/csharp/reference/threads#snooze); CLI [`nextmail threads snooze`](https://nextmail.sh/docs/cli/reference/threads#threads-snooze); MCP [`snoozeThreads`](https://nextmail.sh/docs/mcp/tools/organising#snoozeThreads).

### `POST /threads/{id}/unsnooze`

Unsnooze a thread

Brings it back now and cancels the scheduled return.

Requires the `threads:write` scope.

- Scopes: `threads:write`.

**Path parameters**

- `id` (`string`, required): Thread id.

**Returns**

- `200` `ThreadSnooze`: Back in the inbox, with `snoozedUntil` null.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.threads.unsnooze()`](https://nextmail.sh/docs/sdk/reference/threads#unsnooze); Python [`threads.unsnooze()`](https://nextmail.sh/docs/python/reference/threads#unsnooze); Ruby [`threads.unsnooze`](https://nextmail.sh/docs/ruby/reference/threads#unsnooze); PHP [`threads->unsnooze`](https://nextmail.sh/docs/php/reference/threads#unsnooze); Go [`Threads.Unsnooze`](https://nextmail.sh/docs/go/reference/threads#unsnooze); Java [`threads().unsnooze`](https://nextmail.sh/docs/java/reference/threads#unsnooze); C# [`Threads.UnsnoozeAsync`](https://nextmail.sh/docs/csharp/reference/threads#unsnooze); CLI [`nextmail threads unsnooze`](https://nextmail.sh/docs/cli/reference/threads#threads-unsnooze); MCP [`unsnoozeThreads`](https://nextmail.sh/docs/mcp/tools/organising#unsnoozeThreads).

### `GET /threads/{id}/messages/{messageId}/attachments`

A message's attachments

`content` is base64, and an empty string where the stored bytes could not be found.

This is the DISPLAY list. Of the parts named by a message's `encryption.parts`, the `ciphertext` part IS listed and downloads like any other file (it is the message, and downloading it is the only way an API client reads this mail). The PGP/MIME `version` part and any detached `signature` are held out deliberately, because they rendered as junk attachments and there is nothing a caller can do with them. Both keep their ids in `encryption.parts`, which correlates the two views, and this endpoint does not return them.

Requires the `threads:read` scope.

- Scopes: `threads:read`.

**Path parameters**

- `id` (`string`, required): Thread id the message belongs to.
- `messageId` (`string`, required): Message id from that thread's `messages`.

**Returns**

- `200` `MessageAttachmentList`: Attachments.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.threads.listAttachments()`](https://nextmail.sh/docs/sdk/reference/threads#listAttachments); Python [`threads.list_attachments()`](https://nextmail.sh/docs/python/reference/threads#listAttachments); Ruby [`threads.list_attachments`](https://nextmail.sh/docs/ruby/reference/threads#listAttachments); PHP [`threads->listAttachments`](https://nextmail.sh/docs/php/reference/threads#listAttachments); Go [`Threads.ListAttachments`](https://nextmail.sh/docs/go/reference/threads#listAttachments); Java [`threads().listAttachments`](https://nextmail.sh/docs/java/reference/threads#listAttachments); C# [`Threads.ListAttachmentsAsync`](https://nextmail.sh/docs/csharp/reference/threads#listAttachments); CLI [`nextmail threads list-attachments`](https://nextmail.sh/docs/cli/reference/threads#threads-list-attachments).

### `GET /threads/counts`

Count the mail in each folder

What the sidebar of the app shows: how many conversations each folder holds and how many of them are unread, how many drafts are waiting, how many conversations each of your labels has in each folder, how many inbox conversations sit in each tab, and how many inbox conversations arrived at each address. `unread` is a pseudo-folder whose count is the unread conversations in the inbox, and `draft` counts drafts, which are never unread. A key or an app limited to particular addresses counts only the mail that arrived at them and the drafts written from them, and `address` narrows the counts to one address, never wider than that.

Requires the `threads:read` scope.

- Scopes: `threads:read`.

**Query parameters**

- `address` (`string`, 3 to 320 characters): Count only the mail delivered to this address. One the key does not reach counts nothing rather than failing.

**Returns**

- `200` `MailboxCounts`: The counts.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.threads.counts()`](https://nextmail.sh/docs/sdk/reference/threads#counts); Python [`threads.counts()`](https://nextmail.sh/docs/python/reference/threads#counts); Ruby [`threads.counts`](https://nextmail.sh/docs/ruby/reference/threads#counts); PHP [`threads->counts`](https://nextmail.sh/docs/php/reference/threads#counts); Go [`Threads.Counts`](https://nextmail.sh/docs/go/reference/threads#counts); Java [`threads().counts`](https://nextmail.sh/docs/java/reference/threads#counts); C# [`Threads.CountsAsync`](https://nextmail.sh/docs/csharp/reference/threads#counts); CLI [`nextmail threads counts`](https://nextmail.sh/docs/cli/reference/threads#threads-counts); MCP [`getMailboxCounts`](https://nextmail.sh/docs/mcp/tools/reading#getMailboxCounts).

### `GET /threads/{id}/summary`

Read the summary of a thread

The short AI summary the reading pane shows above a thread. It is written the first time it is asked for and then kept, and the first request after a newer message arrives writes it again, so reading it is cheap. `state` is `ready` with the summary, `pending` while the first one is being written (ask again in a few seconds), or `none` when there is nothing to summarise, such as a thread holding a message that arrived encrypted. While a newer summary is being written, the one before it comes back as `ready`. Summaries spend no AI actions.

Requires the `threads:read` scope.

- Scopes: `threads:read`.

**Path parameters**

- `id` (`string`, required): The thread, as `GET /threads` returns it.

**Returns**

- `200` `ThreadSummary`: The summary, or the state it is in.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.threads.summary()`](https://nextmail.sh/docs/sdk/reference/threads#summary); Python [`threads.summary()`](https://nextmail.sh/docs/python/reference/threads#summary); Ruby [`threads.summary`](https://nextmail.sh/docs/ruby/reference/threads#summary); PHP [`threads->summary`](https://nextmail.sh/docs/php/reference/threads#summary); Go [`Threads.Summary`](https://nextmail.sh/docs/go/reference/threads#summary); Java [`threads().summary`](https://nextmail.sh/docs/java/reference/threads#summary); C# [`Threads.SummaryAsync`](https://nextmail.sh/docs/csharp/reference/threads#summary); CLI [`nextmail threads summary`](https://nextmail.sh/docs/cli/reference/threads#threads-summary); MCP [`getThreadSummary`](https://nextmail.sh/docs/mcp/tools/reading#getThreadSummary).

### `POST /threads/{id}/restore`

Take a thread out of the Bin

Puts a thread back in the inbox, out of the Bin and out of Spam, which is what Restore from Bin and Move to inbox do in the app. It is the undo of `POST /threads/{id}/trash`, and calling it again changes nothing.

Requires the `threads:write` scope.

- Scopes: `threads:write`.

**Path parameters**

- `id` (`string`, required): The thread, as `GET /threads` returns it.

**Returns**

- `200` `RestoredThread`: Back in the inbox.

**Errors**

- `500`: `thread_update_failed`: the labels could not be written, and nothing on the thread changed.
- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.threads.restore()`](https://nextmail.sh/docs/sdk/reference/threads#restore); Python [`threads.restore()`](https://nextmail.sh/docs/python/reference/threads#restore); Ruby [`threads.restore`](https://nextmail.sh/docs/ruby/reference/threads#restore); PHP [`threads->restore`](https://nextmail.sh/docs/php/reference/threads#restore); Go [`Threads.Restore`](https://nextmail.sh/docs/go/reference/threads#restore); Java [`threads().restore`](https://nextmail.sh/docs/java/reference/threads#restore); C# [`Threads.RestoreAsync`](https://nextmail.sh/docs/csharp/reference/threads#restore); CLI [`nextmail threads restore`](https://nextmail.sh/docs/cli/reference/threads#threads-restore); MCP [`modifyLabels`](https://nextmail.sh/docs/mcp/tools/organising#modifyLabels), [`restoreThreads`](https://nextmail.sh/docs/mcp/tools/organising#restoreThreads).

### `GET /threads/{id}/reply-suggestions`

Suggest replies to a thread

Up to 3 short replies to the latest message of the thread, the ones the reading pane offers under it. Each has a `label`, a few words that say what it answers, and a `body`, the reply itself, ready to send with `POST /emails` or to keep with `POST /drafts`. They are written in the language of that message and the voice of the mail this workspace sends, from the conversation, what was written to this correspondent before, what is known about their organisation and, when the message asks for a time, the free and busy times of the calendar.

Suggestions are written once for each new message and kept, so asking again is free until another message arrives. `state` is `ready` with the suggestions, `pending` while they are being written (ask again in a few seconds), or `none` when the message needs no reply: your own reply came last, or it is a newsletter, an automated notice, mail from a no-reply address, spam or an encrypted message, and every thread is `none` in a workspace that turned `replySuggestions` off in its settings. `messageId` names the message they answer. Writing suggestions spends no AI action, and a workspace has suggestions written for at most 500 new messages a day.

Requires the `threads:read` scope.

- Scopes: `threads:read`.

**Path parameters**

- `id` (`string`, required): The thread, as `GET /threads` returns it.

**Returns**

- `200` `ReplySuggestions`: The suggested replies, or the state they are in.

**Errors**

- `409`: `ai_not_configured`: suggesting replies is not available on this server.
- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.threads.replySuggestions()`](https://nextmail.sh/docs/sdk/reference/threads#replySuggestions); Python [`threads.reply_suggestions()`](https://nextmail.sh/docs/python/reference/threads#replySuggestions); Ruby [`threads.reply_suggestions`](https://nextmail.sh/docs/ruby/reference/threads#replySuggestions); PHP [`threads->replySuggestions`](https://nextmail.sh/docs/php/reference/threads#replySuggestions); Go [`Threads.ReplySuggestions`](https://nextmail.sh/docs/go/reference/threads#replySuggestions); Java [`threads().replySuggestions`](https://nextmail.sh/docs/java/reference/threads#replySuggestions); C# [`Threads.ReplySuggestionsAsync`](https://nextmail.sh/docs/csharp/reference/threads#replySuggestions); CLI [`nextmail threads reply-suggestions`](https://nextmail.sh/docs/cli/reference/threads#threads-reply-suggestions); MCP [`getReplySuggestions`](https://nextmail.sh/docs/mcp/tools/writing#getReplySuggestions).

### `POST /threads/{id}/mute`

Mute a thread

Mutes a conversation, as Mute does in the app. A thread in the inbox moves to the archive, and from then on new mail in it goes straight to the archive, unread, with no push, sound or desktop notice. A webhook for that mail carries `muted: true`. Thread state belongs to the whole workspace, so the thread is muted for everyone on it, and mail addressed only to you does not bring it back. Calling it again changes nothing. `PATCH /threads/{id}` with `MUTE` in `addLabelIds` does the same.

Requires the `threads:write` scope.

- Scopes: `threads:write`.

**Path parameters**

- `id` (`string`, required): The thread, as `GET /threads` returns it.

**Returns**

- `200` `MutedThread`: Muted, with `muted` true.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.threads.mute()`](https://nextmail.sh/docs/sdk/reference/threads#mute); Python [`threads.mute()`](https://nextmail.sh/docs/python/reference/threads#mute); Ruby [`threads.mute`](https://nextmail.sh/docs/ruby/reference/threads#mute); PHP [`threads->mute`](https://nextmail.sh/docs/php/reference/threads#mute); Go [`Threads.Mute`](https://nextmail.sh/docs/go/reference/threads#mute); Java [`threads().mute`](https://nextmail.sh/docs/java/reference/threads#mute); C# [`Threads.MuteAsync`](https://nextmail.sh/docs/csharp/reference/threads#mute); CLI [`nextmail threads mute`](https://nextmail.sh/docs/cli/reference/threads#threads-mute); MCP [`muteThreads`](https://nextmail.sh/docs/mcp/tools/organising#muteThreads).

### `POST /threads/{id}/unmute`

Unmute a thread

Takes the mute off, so the next message in the thread arrives in the inbox and alerts people again. The thread stays where it is: move it back with `PATCH /threads/{id}` and `INBOX` if you want it in the inbox now. A thread that is not muted is left as it is.

Requires the `threads:write` scope.

- Scopes: `threads:write`.

**Path parameters**

- `id` (`string`, required): The thread, as `GET /threads` returns it.

**Returns**

- `200` `MutedThread`: Not muted any more, with `muted` false.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.threads.unmute()`](https://nextmail.sh/docs/sdk/reference/threads#unmute); Python [`threads.unmute()`](https://nextmail.sh/docs/python/reference/threads#unmute); Ruby [`threads.unmute`](https://nextmail.sh/docs/ruby/reference/threads#unmute); PHP [`threads->unmute`](https://nextmail.sh/docs/php/reference/threads#unmute); Go [`Threads.Unmute`](https://nextmail.sh/docs/go/reference/threads#unmute); Java [`threads().unmute`](https://nextmail.sh/docs/java/reference/threads#unmute); C# [`Threads.UnmuteAsync`](https://nextmail.sh/docs/csharp/reference/threads#unmute); CLI [`nextmail threads unmute`](https://nextmail.sh/docs/cli/reference/threads#threads-unmute); MCP [`unmuteThreads`](https://nextmail.sh/docs/mcp/tools/organising#unmuteThreads).

### `GET /follow-ups`

List reminders

The reminders you set on mail you sent, newest first: the ones still waiting for a reply and the ones that ended. A reminder is `waiting` until somebody replies (`answered`), its time comes with no reply (`due`, and the thread is back at the top of the inbox, unread) or it is cancelled. An API key lists the reminders of the workspace owner, and an app those of the person who connected it. A key or an app limited to particular addresses lists only reminders on threads delivered to them.

Requires the `threads:read` scope.

- Scopes: `threads:read`.

**Query parameters**

- `status` (`string`, one of `"waiting"`, `"answered"`, `"due"`, `"cancelled"`): Only reminders in this state.
- `threadId` (`string`, 1 to 256 characters): Only the reminders on this thread.
- `limit` (`integer`, at least 1, at most 200): How many to return, newest first. 100 when left out.

**Returns**

- `200` `FollowUpList`: The reminders.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.followUps.list()`](https://nextmail.sh/docs/sdk/reference/follow-ups#list); Python [`follow_ups.list()`](https://nextmail.sh/docs/python/reference/follow-ups#list); Ruby [`follow_ups.list`](https://nextmail.sh/docs/ruby/reference/follow-ups#list); PHP [`followUps->list`](https://nextmail.sh/docs/php/reference/follow-ups#list); Go [`FollowUps.List`](https://nextmail.sh/docs/go/reference/follow-ups#list); Java [`followUps().list`](https://nextmail.sh/docs/java/reference/follow-ups#list); C# [`FollowUps.ListAsync`](https://nextmail.sh/docs/csharp/reference/follow-ups#list); CLI [`nextmail follow-ups list`](https://nextmail.sh/docs/cli/reference/follow-ups#follow-ups-list); MCP [`listFollowUps`](https://nextmail.sh/docs/mcp/tools/organising#listFollowUps).

### `POST /follow-ups`

Set a reminder on a thread

Asks to be reminded about a thread if nobody replies by `remindAt`. If a real reply arrives first, the reminder closes by itself. Otherwise, when the time comes, the thread returns to the top of the inbox, unread, with the built-in `FOLLOW_UP` label and a push to your phones. Automatic replies, delivery reports and your own sending address do not count as a reply. A thread holds one waiting reminder per person, so setting another moves the time. To set one as you send, pass `followUpAt` to `POST /emails` instead.

Requires the `threads:write` scope.

- Scopes: `threads:write`.

**Request body**

- `threadId` (`string`, required, 1 to 256 characters): The thread to be reminded about, as `threads.list` returns it.
- `remindAt` (`string`, required, format `date-time`): When the thread comes back if nobody replied: a future instant, at most 365 days out.

**Returns**

- `200` `FollowUp`: The reminder, waiting.

**Errors**

- `422`: `invalid_follow_up`: `remindAt` is not in the future or is more than 365 days out, or somebody has already replied in the thread since your last message.
- The errors every operation can return: `400`, `401`, `403`, `404`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.followUps.set()`](https://nextmail.sh/docs/sdk/reference/follow-ups#set); Python [`follow_ups.set()`](https://nextmail.sh/docs/python/reference/follow-ups#set); Ruby [`follow_ups.set`](https://nextmail.sh/docs/ruby/reference/follow-ups#set); PHP [`followUps->set`](https://nextmail.sh/docs/php/reference/follow-ups#set); Go [`FollowUps.Set`](https://nextmail.sh/docs/go/reference/follow-ups#set); Java [`followUps().set`](https://nextmail.sh/docs/java/reference/follow-ups#set); C# [`FollowUps.SetAsync`](https://nextmail.sh/docs/csharp/reference/follow-ups#set); CLI [`nextmail follow-ups set`](https://nextmail.sh/docs/cli/reference/follow-ups#follow-ups-set); MCP [`setFollowUp`](https://nextmail.sh/docs/mcp/tools/organising#setFollowUp).

### `DELETE /follow-ups/{id}`

Cancel a reminder

Cancels a waiting reminder, so the thread stays where it is when the time comes. A reminder that already ended is returned as it is. Only your own reminders can be cancelled.

Requires the `threads:write` scope.

- Scopes: `threads:write`.

**Path parameters**

- `id` (`string`, required): The reminder, as the list returns it.

**Returns**

- `200` `FollowUp`: The reminder as it is now.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.followUps.cancel()`](https://nextmail.sh/docs/sdk/reference/follow-ups#cancel); Python [`follow_ups.cancel()`](https://nextmail.sh/docs/python/reference/follow-ups#cancel); Ruby [`follow_ups.cancel`](https://nextmail.sh/docs/ruby/reference/follow-ups#cancel); PHP [`followUps->cancel`](https://nextmail.sh/docs/php/reference/follow-ups#cancel); Go [`FollowUps.Cancel`](https://nextmail.sh/docs/go/reference/follow-ups#cancel); Java [`followUps().cancel`](https://nextmail.sh/docs/java/reference/follow-ups#cancel); C# [`FollowUps.CancelAsync`](https://nextmail.sh/docs/csharp/reference/follow-ups#cancel); CLI [`nextmail follow-ups cancel`](https://nextmail.sh/docs/cli/reference/follow-ups#follow-ups-cancel); MCP [`cancelFollowUp`](https://nextmail.sh/docs/mcp/tools/organising#cancelFollowUp).

### `GET /sender-categories`

List sender choices

The senders whose mail always goes to one inbox tab, by address. New mail is sorted into Primary, Promotions, Updates, Social or Forums by plain rules, and a choice saved here wins over them. `GET /threads` takes `category` to read one tab.

Requires the `threads:read` scope.

- Scopes: `threads:read`.

**Returns**

- `200` `SenderCategoryList`: The choices, by sender.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.threads.listSenderCategories()`](https://nextmail.sh/docs/sdk/reference/threads#listSenderCategories); Python [`threads.list_sender_categories()`](https://nextmail.sh/docs/python/reference/threads#listSenderCategories); Ruby [`threads.list_sender_categories`](https://nextmail.sh/docs/ruby/reference/threads#listSenderCategories); PHP [`threads->listSenderCategories`](https://nextmail.sh/docs/php/reference/threads#listSenderCategories); Go [`Threads.ListSenderCategories`](https://nextmail.sh/docs/go/reference/threads#listSenderCategories); Java [`threads().listSenderCategories`](https://nextmail.sh/docs/java/reference/threads#listSenderCategories); C# [`Threads.ListSenderCategoriesAsync`](https://nextmail.sh/docs/csharp/reference/threads#listSenderCategories); CLI [`nextmail threads list-sender-categories`](https://nextmail.sh/docs/cli/reference/threads#threads-list-sender-categories); MCP [`listSenderCategories`](https://nextmail.sh/docs/mcp/tools/organising#listSenderCategories).

### `PUT /sender-categories/{email}`

Always sort a sender into one tab

Saves the tab for one sender and moves the inbox threads whose newest message is from them, up to the newest 1,000, into it. Mail from them is sorted there from now on, whatever the rules would say. Saving again replaces the choice. A workspace holds at most 2,000 choices, and they apply to every address in it, so a key or an app limited to particular addresses or domains can read them but not change them.

Requires the `threads:write` scope.

- Scopes: `threads:write`.

**Path parameters**

- `email` (`string`, required, up to 320 characters, format `email`): The address of the sender, compared without case.

**Request body**

- `category` (`string`, required, one of `"primary"`, `"promotions"`, `"updates"`, `"social"`, `"forums"`)

**Returns**

- `200` `SenderCategory`: Saved.

**Errors**

- `422`: `invalid_parameter`: the address or the category is not valid. `sender_category_limit_reached`: the workspace holds as many choices as it may. `capability_unsupported`: the key or app is limited to particular addresses or domains.
- The errors every operation can return: `400`, `401`, `403`, `404`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.threads.setSenderCategory()`](https://nextmail.sh/docs/sdk/reference/threads#setSenderCategory); Python [`threads.set_sender_category()`](https://nextmail.sh/docs/python/reference/threads#setSenderCategory); Ruby [`threads.set_sender_category`](https://nextmail.sh/docs/ruby/reference/threads#setSenderCategory); PHP [`threads->setSenderCategory`](https://nextmail.sh/docs/php/reference/threads#setSenderCategory); Go [`Threads.SetSenderCategory`](https://nextmail.sh/docs/go/reference/threads#setSenderCategory); Java [`threads().setSenderCategory`](https://nextmail.sh/docs/java/reference/threads#setSenderCategory); C# [`Threads.SetSenderCategoryAsync`](https://nextmail.sh/docs/csharp/reference/threads#setSenderCategory); CLI [`nextmail threads set-sender-category`](https://nextmail.sh/docs/cli/reference/threads#threads-set-sender-category); MCP [`setSenderCategory`](https://nextmail.sh/docs/mcp/tools/organising#setSenderCategory).

### `DELETE /sender-categories/{email}`

Remove a sender choice

Removes the choice, so new mail from the sender is sorted by the rules again. Threads already sorted stay where they are.

Requires the `threads:write` scope.

- Scopes: `threads:write`.

**Path parameters**

- `email` (`string`, required, up to 320 characters, format `email`): The address of the sender, compared without case.

**Returns**

- `200` `DeletedSenderCategory`: Removed.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.threads.clearSenderCategory()`](https://nextmail.sh/docs/sdk/reference/threads#clearSenderCategory); Python [`threads.clear_sender_category()`](https://nextmail.sh/docs/python/reference/threads#clearSenderCategory); Ruby [`threads.clear_sender_category`](https://nextmail.sh/docs/ruby/reference/threads#clearSenderCategory); PHP [`threads->clearSenderCategory`](https://nextmail.sh/docs/php/reference/threads#clearSenderCategory); Go [`Threads.ClearSenderCategory`](https://nextmail.sh/docs/go/reference/threads#clearSenderCategory); Java [`threads().clearSenderCategory`](https://nextmail.sh/docs/java/reference/threads#clearSenderCategory); C# [`Threads.ClearSenderCategoryAsync`](https://nextmail.sh/docs/csharp/reference/threads#clearSenderCategory); CLI [`nextmail threads clear-sender-category`](https://nextmail.sh/docs/cli/reference/threads#threads-clear-sender-category); MCP [`clearSenderCategory`](https://nextmail.sh/docs/mcp/tools/organising#clearSenderCategory).

### `GET /threads/{id}/messages`

List the messages of a thread

Every message on the thread, oldest first, each as a `MailboxMessage` with its sender, recipients, date, Message-ID, text and HTML body, attachments and headers. Unsent draft replies are in the list with `draft: true`. The fields here are a contract: they are added to, never renamed or taken away, whatever the mailbox stores underneath. `GET /threads/{id}` still returns each message the way the mailbox stored it.

Requires the `threads:read` scope.

- Scopes: `threads:read`.

**Path parameters**

- `id` (`string`, required): The thread, as `GET /threads` returns it.

**Returns**

- `200` `MailboxMessageList`: The messages.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.threads.listMessages()`](https://nextmail.sh/docs/sdk/reference/threads#listMessages); Python [`threads.list_messages()`](https://nextmail.sh/docs/python/reference/threads#listMessages); Ruby [`threads.list_messages`](https://nextmail.sh/docs/ruby/reference/threads#listMessages); PHP [`threads->listMessages`](https://nextmail.sh/docs/php/reference/threads#listMessages); Go [`Threads.ListMessages`](https://nextmail.sh/docs/go/reference/threads#listMessages); Java [`threads().listMessages`](https://nextmail.sh/docs/java/reference/threads#listMessages); C# [`Threads.ListMessagesAsync`](https://nextmail.sh/docs/csharp/reference/threads#listMessages); CLI [`nextmail threads list-messages`](https://nextmail.sh/docs/cli/reference/threads#threads-list-messages).

### `GET /threads/{id}/messages/{messageId}`

Read one message of a thread

One message, in the shape `GET /threads/{id}/messages` lists. The thread is checked first and then the message, so a message that is not on that thread is a 404 even when it exists elsewhere in the mailbox. The fields here are a contract: they are added to, never renamed or taken away, whatever the mailbox stores underneath. `GET /threads/{id}` still returns each message the way the mailbox stored it.

Requires the `threads:read` scope.

- Scopes: `threads:read`.

**Path parameters**

- `id` (`string`, required): The thread, as `GET /threads` returns it.
- `messageId` (`string`, required): The message, as `id` on one of the messages `GET /threads/{id}/messages` returns.

**Returns**

- `200` `MailboxMessage`: The message.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `429`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.threads.getMessage()`](https://nextmail.sh/docs/sdk/reference/threads#getMessage); Python [`threads.get_message()`](https://nextmail.sh/docs/python/reference/threads#getMessage); Ruby [`threads.get_message`](https://nextmail.sh/docs/ruby/reference/threads#getMessage); PHP [`threads->getMessage`](https://nextmail.sh/docs/php/reference/threads#getMessage); Go [`Threads.GetMessage`](https://nextmail.sh/docs/go/reference/threads#getMessage); Java [`threads().getMessage`](https://nextmail.sh/docs/java/reference/threads#getMessage); C# [`Threads.GetMessageAsync`](https://nextmail.sh/docs/csharp/reference/threads#getMessage); CLI [`nextmail threads get-message`](https://nextmail.sh/docs/cli/reference/threads#threads-get-message).

### Objects

#### `DeletedSenderCategory`

`object`

- `object` (`string`, required, one of `"sender_category"`)
- `sender` (`string`, required, format `email`)
- `deleted` (`boolean`, required, one of `true`)

#### `DeletedThread`

`object`

- `object` (`string`, one of `"thread"`)
- `id` (`string`)
- `deleted` (`boolean`, one of `true`)

#### `FollowUp`

`object`

- `object` (`string`, required, one of `"follow_up"`)
- `id` (`string`, required)
- `threadId` (`string`, required, nullable): The thread. Null until a scheduled message with a reminder goes out.
- `messageId` (`string`, required, nullable): The Message-ID of the mail the reminder is about, once it went out.
- `emailId` (`string`, required, nullable): The email, as `GET /emails` lists it, when the reminder was set with `followUpAt`.
- `subject` (`string`, required, nullable): The subject of the thread.
- `remindAt` (`string`, required, format `date-time`): When the thread comes back if nobody replied.
- `status` (`string`, required, one of `"waiting"`, `"answered"`, `"due"`, `"cancelled"`): `waiting` for a reply, `answered` by one, `due` when the time came with none, or `cancelled`.
- `answeredAt` (`string`, required, nullable, format `date-time`): When the reply arrived. Null unless `answered`.
- `firedAt` (`string`, required, nullable, format `date-time`): When the thread was brought back. Null unless `due`.
- `createdAt` (`string`, required, format `date-time`): When the reminder was set.

#### `FollowUpList`

`object`

- `object` (`string`, one of `"list"`)
- `data` (`FollowUp[]`)

#### `KnowledgeSourceRef`

`object`

- `id` (`string`, required): The knowledge item, as `GET /knowledge/{id}` reads it.
- `title` (`string`, required)
- `kind` (`string`, required, one of `"note"`, `"file"`, `"link"`)
- `scope` (`string`, required): A `scope` is empty for the whole workspace, `@` and a domain for everything on that domain (`@acme.com`), or one address (`sales@acme.com`). The AI working for an address reads that address, then its domain, then the whole workspace, the most specific first.
- `url` (`string`, required, nullable): The page a link item reads, or null.

#### `MailboxCounts`

`object`

- `object` (`string`, required, one of `"mailbox_counts"`)
- `folders` (`object[]`, required)
  - `label` (`string`, required, one of `"inbox"`, `"sent"`, `"spam"`, `"archive"`, `"trash"`, `"snoozed"`, `"draft"`, `"unread"`): The folder, as its label id in lower case.
  - `count` (`integer`, required): Conversations in the folder. For `draft`, the drafts waiting to be sent.
  - `unread` (`integer`, required): How many of them are unread. Always 0 for `draft`.
- `labels` (`object[]`, required): One row for each of your labels and each folder it has conversations in. A label with no conversations in a folder has no row for it.
  - `id` (`string`, required): The label, as `GET /labels` lists it.
  - `folder` (`string`, required, one of `"inbox"`, `"sent"`, `"spam"`, `"archive"`, `"trash"`, `"snoozed"`): The folder, as its label id in lower case.
  - `count` (`integer`, required): Conversations in the folder that carry the label.
  - `unread` (`integer`, required): How many of them are unread.
- `categories` (`object[]`, required): One row for each inbox tab, counting inbox conversations only. `primary` is everything not sorted into another tab.
  - `category` (`string`, required, one of `"primary"`, `"promotions"`, `"updates"`, `"social"`, `"forums"`): The tab.
  - `count` (`integer`, required): Inbox conversations in the tab.
  - `unread` (`integer`, required): How many of them are unread.
- `unlabelled` (`object`, required): The inbox conversations that carry none of your labels, which is what the inbox of the app lists unless Labelled mail is ticked in its Filters menu. A conversation the default rule labelled `BOUNCED` counts as labelled.
  - `count` (`integer`, required): Inbox conversations with no label.
  - `unread` (`integer`, required): How many of them are unread.
  - `categories` (`object[]`, required): The same count for each inbox tab.
    - `category` (`string`, required, one of `"primary"`, `"promotions"`, `"updates"`, `"social"`, `"forums"`): The tab.
    - `count` (`integer`, required): Inbox conversations in the tab with no label.
    - `unread` (`integer`, required): How many of them are unread.
- `addresses` (`object[]`, required)
  - `address` (`string`, required, nullable): The address the mail arrived at, or null for mail that recorded none.
  - `count` (`integer`, required): Inbox conversations delivered to it.
- `team` (`object`, required): The team views of a shared inbox, counted for the person the key or app acts for. A conversation is open while it is in the inbox, and marking it done archives it.
  - `assigned` (`object`, required): Open conversations assigned to you.
    - `count` (`integer`, required): Open conversations assigned to you.
    - `unread` (`integer`, required): How many of them are unread.
  - `unassigned` (`object`, required): Open conversations nobody is assigned to.
    - `count` (`integer`, required): Open conversations nobody has.
    - `unread` (`integer`, required): How many of them are unread.
  - `mentions` (`object`, required): Conversations outside Spam and the bin where a team comment mentions you.
    - `count` (`integer`, required): Conversations that mention you.
    - `unread` (`integer`, required): How many of them are unread.
    - `unseen` (`integer`, required): How many of them have a mention newer than the last time you opened their comments in the app.

#### `MailboxMessage`

`object`

A message in the mailbox, in a shape that does not change with how it is stored. The fields here are a contract: they are added to, never renamed or taken away, whatever the mailbox stores underneath. `GET /threads/{id}` still returns each message the way the mailbox stored it.

- `object` (`string`, required, one of `"message"`)
- `id` (`string`, required): The message, `msg_` and 24 hex characters, for the message and attachment routes.
- `threadId` (`string`, required): The conversation it belongs to.
- `messageId` (`string`, required, nullable): Its Message-ID header, angle brackets included. A message that arrived without one has one Nextmail made up.
- `from` (`object`, required): Who it is from.
  - `name` (`string`, required, nullable): The display name, or null when there was none.
  - `email` (`string`, required): The address.
- `to` (`object[]`, required): Its To recipients.
  - `name` (`string`, required, nullable): The display name, or null when there was none.
  - `email` (`string`, required): The address.
- `cc` (`object[]`, required): Its Cc recipients.
  - `name` (`string`, required, nullable): The display name, or null when there was none.
  - `email` (`string`, required): The address.
- `bcc` (`object[]`, required): Its Bcc recipients. Only mail the workspace sent knows them, so mail that arrived has none.
  - `name` (`string`, required, nullable): The display name, or null when there was none.
  - `email` (`string`, required): The address.
- `replyTo` (`object[]`, required): Where a reply goes, when that is not the sender.
  - `name` (`string`, required, nullable): The display name, or null when there was none.
  - `email` (`string`, required): The address.
- `subject` (`string`, required): The subject line, `(no subject)` when it had none.
- `date` (`string`, required, nullable, format `date-time`): When it was written, from its Date header. Null when that header could not be read.
- `draft` (`boolean`, required): True for a reply written in the app and not sent yet.
- `inReplyTo` (`string`, required, nullable): The Message-ID of the message it answers, from its In-Reply-To header.
- `references` (`string[]`, required): The Message-IDs of its References header, oldest first.
- `text` (`string`, required, nullable): The body as plain text. For a message that came as HTML only, this is the text drawn from that HTML, with links written out after their words. Null when it has no body.
- `html` (`string`, required, nullable): The HTML body as it arrived or was sent, unprocessed. Null for a plain text message.
- `attachments` (`object[]`, required): Its attachments, without their bytes. An image shown inside the body is not listed. Read the bytes with `GET /threads/{id}/messages/{messageId}/attachments`.
  - `id` (`string`, required): The attachment, as `attachmentId` in the attachment list.
  - `filename` (`string`, required)
  - `mimeType` (`string`, required)
  - `size` (`integer`, required): Its size in bytes.
- `headers` (`object[]`, required): The headers Nextmail keeps of the message, written out as it would send them: From, To, Cc, Reply-To, Subject, Date, Message-ID, In-Reply-To, References, List-Unsubscribe, List-Unsubscribe-Post. A header the message did not have is left out. The rest of what it arrived with is not kept.
  - `name` (`string`, required)
  - `value` (`string`, required)
- `encryption` (`object`, required, nullable): Set when the message is signed or encrypted. `sealed` is true when its body cannot be read without the key, so `text` holds armour or nothing. Null for ordinary mail.
  - `format` (`string`, required, one of `"pgp-mime"`, `"pgp-signed"`, `"pgp-inline"`, `"smime-encrypted"`, `"smime-signed"`)
  - `sealed` (`boolean`, required)

#### `MailboxMessageList`

`object`

- `object` (`string`, one of `"list"`)
- `data` (`MailboxMessage[]`)

#### `MessageAttachmentList`

`object`

- `object` (`string`, required, one of `"list"`)
- `data` (`object[]`, required)
  - `object` (`string`, required, one of `"attachment"`)
  - `attachmentId` (`string`, required)
  - `filename` (`string`, required)
  - `contentType` (`string`, required)
  - `size` (`integer`, required, at least 0): Bytes.
  - `content` (`string`, required, format `byte`): The bytes, base64. An empty string when the stored bytes could not be found.

#### `MessageEncryption`

`object`

What kind of encryption this message arrived carrying, read off its top-level `Content-Type` when it was ingested.

ABSENCE MEANS NOBODY LOOKED. The field is omitted on every message stored before detection existed. It never means "checked, and found none". A client reading a missing `encryption` as "this was plaintext" is asserting something no part of this system measured.

It is a statement about the ENVELOPE and not a verification. A message can be seen to be sealed without being opened, and those are different claims: nothing here says a signature checked out, says who holds a key, or licenses a padlock in a user interface.

SIGNED IS NOT SEALED, and that distinction is the whole reason `format` is an enum rather than a flag. For the three sealed formats the message's body fields are empty or hold PGP armor, and an empty body on such a message means "we cannot read this" rather than "there was nothing here". For the two signed formats the body is ordinary text and every consumer keeps working on it.

- `format` (`string`, required, one of `"pgp-mime"`, `"pgp-signed"`, `"pgp-inline"`, `"smime-encrypted"`, `"smime-signed"`): Which of the five envelope shapes was recognised. THREE of them mean the body is unreadable (`pgp-mime`, `pgp-inline` and `smime-encrypted`), and the other two, `pgp-signed` and `smime-signed`, mean the body arrived in the clear beside a detached signature and is read exactly like any other message. Branch on this value; branching on the mere presence of the object treats readable mail as unreadable.
- `detectedAt` (`string`, required): When the classification was made, on our clock at ingest. It dates OUR READING of the envelope and says nothing about when the message was encrypted or signed, or by whom.
- `rawRetained` (`boolean`, default `false`): Whether the original RFC822 bytes were kept, so that something holding the key could open the message later. Today it is always false. Nothing retains the raw message yet. It is published now so a client can start reading it rather than needing a second pass over every stored message the day that changes.
- `parts` (`object[]`): The envelope parts, each named by the id its bytes were written under. They are deliberately NOT in the message's attachment list (a PGP/MIME message would otherwise render two junk chips), so a sealed message commonly reports no attachments at all, and that emptiness is not evidence that nothing was attached. These ids are a CORRELATION KEY and not a handle: `GET /threads/{id}/messages/{messageId}/attachments` serves the display list, so it does not return them, and no other path in this API returns them either. `index` is the index into the original MIME parts.
  - `index` (`integer`, required)
  - `attachmentId` (`string`, required)
  - `role` (`string`, required, one of `"version"`, `"ciphertext"`, `"signature"`)

#### `MutedThread`

`object`

- `object` (`string`, required, one of `"thread"`)
- `id` (`string`, required)
- `muted` (`boolean`, required): Whether the thread is muted now.

#### `ReplySuggestion`

`object`

- `label` (`string`, required, up to 60 characters): What the reply says, in a few words, as a button would show it.
- `body` (`string`, required, up to 1200 characters): The reply in plain text, with no subject and no signature.

#### `ReplySuggestions`

`object`

- `object` (`string`, required, one of `"reply_suggestions"`)
- `threadId` (`string`, required)
- `messageId` (`string`, required, nullable): The message the suggestions answer, the latest one in the thread.
- `state` (`string`, required, one of `"ready"`, `"pending"`, `"none"`)
- `suggestions` (`ReplySuggestion[]`, required, up to 3 items): Empty unless `state` is `ready`.
- `sources` (`KnowledgeSourceRef[]`, required): The knowledge base items the suggestions drew on: pinned notes at the levels of the receiving address and the passages that matched the message. Empty when none were used.

#### `RestoredThread`

`object`

- `object` (`string`, one of `"thread"`)
- `id` (`string`)
- `restored` (`boolean`, one of `true`)

#### `SenderCategory`

`object`

- `object` (`string`, required, one of `"sender_category"`)
- `sender` (`string`, required, format `email`): The sender, in lower case.
- `category` (`string`, required, one of `"primary"`, `"promotions"`, `"updates"`, `"social"`, `"forums"`): The inbox tab their mail goes to.
- `updatedAt` (`string`, required, format `date-time`): The last save.

#### `SenderCategoryList`

`object`

- `object` (`string`, one of `"list"`)
- `data` (`SenderCategory[]`)

#### `Thread`

`object`

- `object` (`string`, one of `"thread"`)
- `id` (`string`): The id that was asked for. Echoed back rather than read off the result. The mailbox answers a thread as messages, labels and counts, with no id of its own.
- `messages` (`ThreadMessage[]`): Every message on the thread, oldest first, not only the most recent.
- `labels` (`object[]`): The folders and labels the thread sits in. Backend-shaped like the messages are, so only the two fields every backend agrees on are described.
  - `id` (`string`)
  - `name` (`string`)
- `messageCount` (`integer`): The length of `messages`, counted here.
- `hasUnread` (`boolean`)
- `totalReplies` (`integer`)
- `deliveredTo` (`string`, nullable): The workspace address the thread belongs to, lower-cased: the address its first message was delivered to, with any `+tag` removed, or the address it was sent from when the thread began with a send. Later messages joining the thread do not change it. Null when no address was recorded.

#### `ThreadLabelChange`

`object`

- `object` (`string`, required, one of `"thread"`)
- `id` (`string`, required)
- `addedLabelIds` (`string[]`, required): The labels put on, with `UNREAD` when `read` was false.
- `removedLabelIds` (`string[]`, required): The labels taken off, with `UNREAD` when `read` was true. Unknown ids are passed through.
- `updatedThreadIds` (`string[]`, required): The conversations the change was written to.
- `failedThreadIds` (`string[]`, required): The conversations it could not be written to.

#### `ThreadList`

`object`

- `object` (`string`, required, one of `"list"`)
- `data` (`object[]`, required)
  - `object` (`string`, required, one of `"thread"`)
  - `id` (`string`, required)
- `nextPageToken` (`string`, required, nullable): Pass it back as `pageToken` for the next page. Null on the last page.

#### `ThreadMessage`

`object`

A message, in whatever shape the mailbox stored it. The fields are deliberately not enumerated here. `encryption`, `sentWith`, `forwardedFrom` and `verificationCode` are the properties this API describes, because their absence cannot be guessed at safely.

- `encryption` (`MessageEncryption`)
- `sentWith` (`string`, one of `"openemail"`, `"gmail"`, `"outlook"`, `"icloud"`, `"yahoo"`, `"proton"`, `"zoho"`, `"fastmail"`, `"resend"`, `"sendgrid"`, `"mailgun"`, `"postmark"`, `"mailchimp"`, `"brevo"`, `"sparkpost"`, `"hubspot"`, `"amazon-ses"`): What the message was sent with, read from its headers when it arrived: a mail provider such as `gmail` or `outlook`, a sending service such as `resend` or `sendgrid`, `openemail` for mail sent through Nextmail, or `amazon-ses` when only the network it came through is known. Absent when nothing in the message says.
- `forwardedFrom` (`string`, nullable): The address the message was first sent to, when it reached you through forwarding rather than directly. A reply cannot come from that address unless it is one of yours. Null when the message came to you directly, and absent on messages that arrived before this was recorded.
- `verificationCode` (`string`, nullable): The sign-in or verification code the message carries, as you would type it: digits only, or capital letters and digits, without spaces or dashes. Set only when Nextmail is sure: the sender passed authentication, nothing about the message was flagged, it was not filed to Spam or Trash, and it holds exactly one code worded as one. Null when the message carries none or any of that is in doubt. Absent where a listing leaves out message bodies and the message arrived before codes were read.

#### `ThreadSnooze`

`object`

- `object` (`string`, required, one of `"thread"`)
- `id` (`string`, required)
- `snoozedUntil` (`string`, required, nullable, format `date-time`): When the thread comes back to the inbox. Null once it is unsnoozed.

#### `ThreadSummary`

`object`

- `object` (`string`, required, one of `"thread_summary"`)
- `threadId` (`string`, required)
- `state` (`string`, required, one of `"ready"`, `"pending"`, `"failed"`, `"none"`): `ready` when the summary is written. `pending` while it is being written, so ask again in a few seconds. `failed` when the last try did not work, and asking again tries once more. `none` when there is nothing to summarise.
- `summary` (`string`, required, nullable): One or two sentences, present when `state` is `ready`.

#### `TrashedThread`

`object`

- `object` (`string`, required, one of `"thread"`)
- `id` (`string`, required)
- `trashed` (`boolean`, required, one of `true`)
