---
title: "Threads, drafts and labels"
description: "Every command in the threads, drafts and labels namespaces, and how they sit under inbox, read, archive and the other mail commands."
url: "https://nextmail.sh/docs/cli/mailbox"
area: "CLI"
category: "Commands by area"
---

# Threads, drafts and labels

Every command in the threads, drafts and labels namespaces, and how they sit under inbox, read, archive and the other mail commands.

## Overview

The mail commands, such as `inbox`, `read`, `archive` and `label add`, are written for people: they take several thread ids at once, format what they print and keep the label ids out of sight. Each of them runs commands from this page, which are the SDK methods for threads, drafts and labels, one command per method, so `threads.listAttachments` is `nextmail threads list-attachments`.

Use these when you need what the mail commands leave out: a thread exactly as the API returns it, the files on a message, drafts, and making, renaming, recolouring or deleting labels.

- `nextmail thread` and `nextmail draft` work as well as the plural names. `nextmail labels` has no singular form: `nextmail label` is the mail command that puts labels on threads.
- The verbs take the usual aliases: `ls` for `list`, `show` and `view` for `get`, `new` and `add` for `create`, `edit` for `update`, and `rm`, `del` and `remove` for `delete`.
- Every flag is in `nextmail <namespace> <verb> --help`, such as `nextmail threads list --help`.

## Threads

Conversations in the mailbox. A thread id such as `CAHk7pQ2x9LmZ4` comes from `threads list`, `nextmail inbox` or `nextmail search`.

| Command | What it does |
| --- | --- |
| nextmail threads list | List one page of threads in a folder, newest first. Each row is only an id. `--folder`, `--query`, `--label-ids`, `--sort`, `--date-from`, `--date-to`, `--from-contacts` and `--semantic` narrow and order it |
| nextmail threads get <id> | Read a thread with every message on it, oldest first, with its labels and unread state |
| nextmail threads update <id> | Mark a thread read with `--read` or unread with `--no-read`, and put labels on or take them off with `--add-label-ids` and `--remove-label-ids`, up to 50 each |
| nextmail threads trash <id> | Move a thread to the Bin, out of the inbox, spam, snoozed and archive in one step. Asks you to confirm |
| nextmail threads snooze <id> <wake-at> | Hide a thread until a future instant, such as `2026-10-01T09:00:00Z`. Snoozing it again replaces the wake time |
| nextmail threads unsnooze <id> | Bring a snoozed thread back to the inbox now and clear its wake time |
| nextmail threads list-attachments <id> <message-id> | List one message's attachments, each with its bytes inline as base64 in `content` |
| nextmail threads list-notes <id> | List the private notes on a thread, pinned ones first |
| nextmail threads create-note <id> | Pin a note to a thread with `--content`, and optionally `--color` and `--pinned` |
| nextmail threads update-note <id> <note-id> | Change the text, the colour or the pin of a note |
| nextmail threads delete-note <id> <note-id> | Delete a note for good. Asks you to confirm |
| nextmail threads reorder-notes <id> | Arrange the notes on a thread with `--ids`, naming every one of them, first to last |
| nextmail threads get-team <id> | Read who has a thread, who marked it done, its comment count, who is replying and everyone who can see it |
| nextmail threads assign <id> <assignee-id> | Give a thread to one person who can see it. They are emailed unless they took it themselves |
| nextmail threads unassign <id> | Take a thread back from whoever has it |
| nextmail threads mark-done <id> | Archive a thread for everyone and record who marked it done |
| nextmail threads reopen <id> | Move a done thread back to the inbox for everyone |
| nextmail threads list-comments <id> | List the team comments on a thread, oldest first |
| nextmail threads create-comment <id> | Comment on a thread with `--body`, and mention people with `--mentions`, who are emailed |
| nextmail threads update-comment <id> <comment-id> | Change the text and the mentions of a comment you wrote |
| nextmail threads delete-comment <id> <comment-id> | Delete a comment for good. Asks you to confirm |
| nextmail threads counts | Count the conversations in each folder and how many are unread, the drafts waiting, each label in each folder, and the inbox at each address. `--address` counts the mail of one |
| nextmail threads summary <id> | Read the AI summary of a thread. `state` is `pending` while one is being written, so ask again a few seconds later |
| nextmail threads restore <id> | Take a thread back out of the Bin or Spam and put it in the inbox |
| nextmail threads delete <id> | Delete a thread for good, every message and attachment in it. Asks you to confirm |
| nextmail threads unsubscribe <id> | Unsubscribe from the sender of a thread, as the Unsubscribe button of the reading pane does. Asks you to confirm |
| nextmail threads reply-suggestions <id> | Suggest up to three short replies to the latest message of a thread, each with a `label` and a `body`. `state` is `pending` while they are being written, so ask again a few seconds later, and `none` when the message needs no reply |
| nextmail threads mute <id> | Mute a thread: it leaves the inbox for the archive, and new mail in it arrives archived, unread and with no notification |
| nextmail threads unmute <id> | Take the mute off, so the next message arrives in the inbox again. The thread stays where it is |
| nextmail threads list-sender-categories | List the senders whose mail always goes to one inbox tab |
| nextmail threads set-sender-category <email> <category> | Always sort one sender into `primary`, `promotions`, `updates`, `social` or `forums`, and move their inbox threads there |
| nextmail threads clear-sender-category <email> | Remove the tab chosen for a sender, so the rules sort their mail again. Asks you to confirm |

- `--folder` defaults to `inbox` and is matched as a label id, so `sent`, `archive`, `spam`, `trash`, `draft`, `snoozed`, `starred` and `unread` work, `bin` reads as `trash`, and a user label id such as `USER_RECEIPTS` works too. A folder that matches nothing returns an empty page, not an error.
- `--query` takes the search syntax of the app, and `in:anywhere` searches every folder. `--label-ids` narrows further, since a thread must carry the folder and every id you pass. `--date-from` and `--date-to` read the newest message on each thread, and both ends are included.
- `threads get` includes unsent draft replies among the messages, marked `isDraft: true`, and it opens a draft id too.
- `threads update` needs `--read`, `--no-read` or a label to add or remove. Removals are applied before additions. A label id that names no label is refused with `label_not_found` and nothing on the thread changes, so create the label first. `TRASH`, `SNOOZED` and `DRAFT` are refused with `label_not_directly_settable`: use `threads trash` and `threads snooze`.
- `threads trash` deletes nothing: the thread stays readable with `threads get`, and `threads restore` takes it back out of the Bin. Trashing a snoozed thread also cancels its wake, and `threads delete` removes a thread for good from any folder.
- `threads snooze` sends `<wake-at>` as it is, so give it a future ISO 8601 instant with `Z` or an offset, since a time without one is read in the time zone of the server. A delay such as `3h` is refused as invalid. `nextmail snooze --until 3h` takes a delay. Threads wake at an hourly sweep, up to about an hour late, and always into the inbox.
- `threads list-attachments` returns every file whole in one response. Take the message id from the `messages` of `threads get`. `content` is an empty string when the stored bytes cannot be found, so check its length before decoding.
- Notes are private to a person. With an API key the note commands read and write the notes of the workspace owner, and after a browser sign-in your own. `--color` is one of `default`, `red`, `orange`, `yellow`, `green`, `blue`, `purple` and `pink`.
- Comments and assignments belong to the team, and the sender never sees them. Only people who can see a thread can be given it or mentioned on it: the workspace owner, members who reach every address, and members who hold its address or its domain. Take their ids from `threads get-team`. With an API key the commands act as the workspace owner, so a comment is theirs. Only its author can change it, and its author or the owner can delete it.
- A mute applies to everyone on the workspace, and mail addressed only to you does not bring a muted thread back. `threads update --add-label-ids MUTE` does the same as `threads mute`, and `threads list --query "is:muted in:anywhere"` finds the muted threads in every folder.
- `threads list --category promotions` reads one inbox tab, and `threads counts` has a row for each tab under `categories`. A choice saved with `threads set-sender-category` wins over the sorting rules. A workspace holds at most 2,000 choices, and a sign-in or key limited to particular addresses can read them but not change them.

## Drafts

Unsent messages saved in the mailbox. A draft id starts with `draft-`.

| Command | What it does |
| --- | --- |
| nextmail drafts list | List one page of drafts, most recently saved first. Each row is only an id, and `--query` searches them |
| nextmail drafts get <id> | Read a draft's recipients, subject, body, sender, the thread it replies to and the names of its attachments |
| nextmail drafts create | Save a new draft from `--to`, `--cc`, `--bcc`, `--subject`, `--html`, `--text`, `--from`, `--thread-id` and `--forward-of`, all optional |
| nextmail drafts update <id> | Change fields on a saved draft. A field you leave out keeps its value |
| nextmail drafts delete <id> | Delete a draft for good. It does not go to the Bin. Asks you to confirm |

- `drafts list --query` searches the subject, the sender and the start of the body, and never leaves drafts. `older_than:30d` and the other date operators read when the draft was last saved, and `to:`, `cc:` and `bcc:` match nothing on a draft.
- A draft is stored as a thread labelled `DRAFT`, so `threads get` opens one and `nextmail inbox draft` lists them. `drafts get`, `update` and `delete` refuse an ordinary thread id with a 404.
- A bare `nextmail drafts create` saves a blank draft. Only lengths are checked: a subject up to 998 characters, and `--html` and `--text` up to 1,000,000 each, with `--html` kept when both are set. There is no flag for attachments.
- `drafts update` replaces each field you send. A list replaces the stored one whole, so `--to` with one address drops the others. The attachment list stays as it is, and a draft that forwards a message keeps forwarding it unless `--thread-id` moves it to another thread.
- `--thread-id` records the thread a draft replies to, but the draft is still stored as a thread of its own.
- Running `drafts create` again saves a second draft, because it takes no idempotency key. A display name with a comma in it splits into two broken recipients, so leave the comma out.
- `nextmail send --draft <id> --to <address>` sends a draft. The body comes from the draft, and so does the subject unless you pass `--subject`, while the recipients are the ones you name. It cannot be combined with a body, `--template` or `--translate`.
- `drafts create --thread-id <id> --forward-of <message-id>` saves a forward of a message in that thread, with `Fwd:` in front of its subject unless you pass `--subject`. Without `--thread-id`, or with a message the thread does not hold, it is 422 `invalid_parameter`. Sending it with `nextmail send --draft <id> --to <address>` quotes the message below the body and attaches its files.

## Labels

The labels a thread can carry. A user label id is `USER_` followed by the name it was created with, upper cased, with each run of whitespace turned into `_`, so `Big Clients` is `USER_BIG_CLIENTS`.

| Command | What it does |
| --- | --- |
| nextmail labels list | List the workspace's user labels, sorted by name, each with its colour, `threadCount`, `createdAt` and `updatedAt` |
| nextmail labels list-colors | List the palette the app offers, fourteen solid colours and seven gradients. `value` is what to pass as a colour |
| nextmail labels get <id> | Read one user label, with its id matched case sensitively |
| nextmail labels create --name <value> | Create a user label. `--color-background-color` gives it a colour |
| nextmail labels update <id> | Rename or recolour a label. The id stays, and so do the threads that carry it |
| nextmail labels delete <id> | Delete a label and take it off every thread that carried it. Asks you to confirm |
| nextmail labels test <id> | Try the AI instructions of a label on the newest 20 inbox threads and list the ones it would catch, without labelling anything. `--ai-instructions` tries wording you have not saved |

- An id never changes, even after a rename, so store ids rather than names.
- System labels such as `INBOX`, `STARRED` and `UNREAD` are not listed and cannot be changed or deleted, though `threads update` takes them. `labels get` on one is a 404.
- A workspace holds up to 50 user labels. A name another label already has, compared without case, is refused with `label_name_taken`.
- A colour is a hex value such as `#3B82F6` or a gradient token such as `gradient:sunset`. `--label-color` takes the whole colour as JSON, and `--label-color null` clears it.
- A label belongs to the workspace, so renaming, recolouring or deleting it changes it for everyone in it.
- `labels delete` has no undo. Creating a label with the same name again gives the same id, but the threads do not get it back. Its `threadCount` in `labels get` says how many conversations will lose it.
- `labels create` and `labels update` take `--ai-instructions`, what the label is for in plain words and at most 500 characters, and `--ai-enabled`, which has AI add the label to arriving mail that fits. It needs a paid plan, or the command is refused with 403 `plan_required`, and a workspace may have 10 such labels, with 422 `ai_label_limit_reached` past that.
- `labels test` spends one AI action of the caller, which the labelling of arriving mail never does. With none left today it stops with 429 `ai_quota_exceeded`.

## How the mail commands use them

| Mail command | What it runs |
| --- | --- |
| inbox [folder] | `threads list` for one page, then `threads get` on each thread, six at a time |
| search <query...> | `threads list --query`, then `threads get` on each thread |
| read <thread-id> | `threads get`, then `threads update --read` unless you pass `--no-mark-read` |
| reply <thread-id> | `threads get` for the recipients, subject and sending address, then `emails send` into the thread |
| archive <thread-id...> | `threads update --add-label-ids ARCHIVE --remove-label-ids INBOX` |
| unarchive <thread-id...> | `threads update --add-label-ids INBOX --remove-label-ids ARCHIVE` |
| star, unstar <thread-id...> | `threads update` adding or removing `STARRED` |
| mark read, unread <thread-id...> | `threads update --read`, or `--no-read` |
| trash <thread-id...> | `threads trash` |
| snooze <thread-id...> --until <when> | `threads snooze`, with a delay such as `3h` turned into an instant first |
| unsnooze <thread-id...> | `threads unsnooze` |
| label add, remove <thread-id...> | `threads update --add-label-ids`, or `--remove-label-ids` |
| send --draft <id> | `emails send --draft-id` |

- A mail command takes several thread ids and reports on each one, and with `--json` it prints `{ results, succeeded, failed }`. A command on this page takes one id and prints what the API returns.
- `nextmail inbox` reads every thread it lists to show who wrote last and the subject. `threads list` makes one request per page and prints only ids, which is all a pipeline needs.
- `nextmail read` turns an HTML message into text and marks the thread read. `threads get` prints the thread as the API returns it and changes nothing.

- [Mail](https://nextmail.sh/docs/cli/mail.md): Inbox, search, read, reply and filing threads.

## Examples

Mark a thread read, archive it and label it in one request, where `mark read`, `archive` and `label add` would make three:

**One update**

```
nextmail threads update CAHk7pQ2x9LmZ4 --read --add-label-ids ARCHIVE,USER_RECEIPTS --remove-label-ids INBOX --json
```

Make a label and file every matching thread under it. Piped, `--all` prints one JSON object per line:

**Label a search**

```
nextmail labels create --name Receipts --color-background-color gradient:meadow
nextmail threads list --query "in:anywhere subject:receipt newer_than:1y" --all | jq -r .id | xargs nextmail label add --label USER_RECEIPTS
```

Save one file from a message. The message ids are on the `messages` of `threads get`:

**Save an attachment**

```
nextmail threads get CAHk7pQ2x9LmZ4 --json | jq -r ".messages[].id"
nextmail threads list-attachments CAHk7pQ2x9LmZ4 message_4c1b257a --json | jq -r '.[] | select(.filename == "invoice.pdf") | .content' | base64 --decode > invoice.pdf
```

Write a draft, change it, read it back, then send it:

**Draft, then send**

```
DRAFT=$(nextmail drafts create --to grace@example.com --subject "Engine notes for Thursday" --html "<p>Agenda below.</p>" --json | jq -r .id)
nextmail drafts update "$DRAFT" --to grace@example.com,charles@example.com
nextmail drafts get "$DRAFT"
nextmail send --draft "$DRAFT" --from you@acme.com --to grace@example.com,charles@example.com
```

Clear out drafts nobody has saved for 30 days. The dry run prints each `DELETE` without sending it, and `--yes` answers the confirmation:

**Old drafts**

```
nextmail drafts list --query older_than:30d --all | jq -r .id > stale.txt
xargs -n 1 nextmail drafts delete --dry-run < stale.txt
xargs -n 1 nextmail drafts delete --yes < stale.txt
```

Pick a gradient from the palette, preview the change, make it, and later take the colour off again:

**Recolour a label**

```
nextmail labels list-colors --json | jq -r '.[] | select(.kind == "gradient") | .value'
nextmail labels update USER_RECEIPTS --name "Receipts 2026" --color-background-color gradient:aurora --dry-run
nextmail labels update USER_RECEIPTS --name "Receipts 2026" --color-background-color gradient:aurora
nextmail labels update USER_RECEIPTS --label-color null
```

## Scopes and verification codes

| Scope | Commands |
| --- | --- |
| threads:read | `threads list`, `get`, `list-attachments`, `list-notes`, `get-team`, `list-comments`, `counts`, `summary` and `list-sender-categories`, and `follow-ups list` |
| threads:write | `threads update`, `trash`, `restore`, `delete`, `snooze`, `unsnooze`, `mute`, `unmute`, `set-sender-category`, `clear-sender-category`, `create-note`, `update-note`, `delete-note`, `reorder-notes`, `assign`, `unassign`, `mark-done`, `reopen`, `create-comment`, `update-comment` and `delete-comment`, and `follow-ups set` and `cancel` |
| drafts:read | `drafts list` and `get` |
| drafts:write | `drafts create`, `update` and `delete` |
| labels:read | `labels list`, `list-colors` and `get` |
| labels:write | `labels create`, `update` and `delete`, and `labels test`, which needs `threads:read` too |

A missing scope stops with exit code `4`. None of these commands asks for a verification code, with a browser sign-in or with an API key.

A sign-in or key limited to some addresses sees only the threads delivered to them, and any other thread is a 404, as if it did not exist. Labels belong to the workspace, so it still sees every label, but `threadCount` counts only the conversations it can see.

## Pages, confirmations and dry runs

- `threads list`, `drafts list` and `labels list` read one page, 25 unless `--limit` says otherwise, up to 100. `--cursor` carries on from the cursor a page printed. A thread cursor keeps the order it was handed out in, so send the same filters with it.
- `--all` reads every page and `--max <n>` stops after that many. Piped or with `--ndjson` it prints one JSON object per line, and with `--json` one `{ items, hasMore, nextCursor }` document.
- `hasMore` can be true on what turns out to be the last page, and the next call then returns no items. A thread that gets new mail while you page moves ahead of the cursor and is not returned by later pages, and so does a draft saved while you page.
- `threads trash`, `threads delete`, `drafts delete` and `labels delete` ask you to confirm. Unattended, with `--json`, `--no-input` or no terminal, they stop with exit code `2` and change nothing unless you pass `--yes`.
- `--dry-run` prints the request a command would send, with the credential redacted, and exits with code `0` without sending it or asking to confirm. With `--json` it prints `{ dryRun, request }`.
- `threads clear-sender-category` and `follow-ups cancel` ask you to confirm as well, and stop the same way unattended unless you pass `--yes`.

## JSON bodies and clearing a field

`--data` takes the whole body as JSON, inline, from a file with `@path`, or from stdin with `-`, and a flag you pass as well overrides its key.

An empty flag value is a usage error, so a field that an empty value clears goes through `--data` instead. `--label-color null` clears a label’s colour.

**Terminal**

```
nextmail drafts update draft-5f0c2a9e-8b1d-4e7a-a3c6-2d9f41b7e0c8 --data '{"from":""}'
nextmail drafts update draft-5f0c2a9e-8b1d-4e7a-a3c6-2d9f41b7e0c8 --data '{"threadId":""}'
nextmail drafts update draft-5f0c2a9e-8b1d-4e7a-a3c6-2d9f41b7e0c8 --data '{"to":[]}'
nextmail drafts create --data @draft.json --subject "Overrides the file"
```

The first saves the draft with no sender, the second detaches it from the thread it replied to, and the third clears its recipients.

## Every flag

**Terminal**

```
nextmail threads --help
nextmail threads list --help
nextmail drafts create --help --json
```

`nextmail <namespace> <verb> --help` shows each argument and flag with its type, the scopes the call needs, its method and path, what it returns and the notes from the API reference. Add `--json` for the same help as data.

- [Commands](https://nextmail.sh/docs/cli/commands.md): How a command reads, the global flags and every namespace.
- [Scripting](https://nextmail.sh/docs/cli/scripting.md): JSON output, streams, exit codes and running unattended.
- [Threads in the SDK](https://nextmail.sh/docs/sdk/threads.md): The same calls from TypeScript.

## Encryption keys

`nextmail encryption` manages the OpenPGP public keys that let senders seal mail to your addresses, the Encryption page of the app. Only public keys pass through it: the private key stays on the device that made it.

| Command | What it does |
| --- | --- |
| nextmail encryption list-keys | List the keys published for your addresses, live and retired, newest first |
| nextmail encryption publish-key --address <value> --public-key <value> --fingerprint <value> | Publish a public key for one of your addresses. `--replaces` rotates the live key. Asks a browser sign-in for a verification code |
| nextmail encryption lookup-keys --addresses <addresses> | Find the live keys of each recipient before sealing a message. An address with no key comes back empty |

- `--public-key @key.asc` reads the armored key from a file, and `-` reads it from stdin.
- With an API key the commands act on the keys of the workspace owner, and after a browser sign-in on your own.

## Assistant chats

| Command | What it does |
| --- | --- |
| nextmail chats list | List the chats held with the assistant in the app, newest first. `--q` searches the titles |
| nextmail chats get <id> | Read one chat: its title, how many messages it holds and when it last changed |
| nextmail chats rename <id> <title> | Give a chat a new title |
| nextmail chats delete <id> | Delete a chat and every message in it, for good. Asks you to confirm |

- Each person reaches only their own chats: an API key reaches the chats of the workspace owner, and a browser sign-in reaches the chats of the person who signed in.

## Subscriptions, senders and analytics

The newsletters and mailing lists the mailbox receives, what is known about the organisation behind a sender, and the numbers behind the Analytics page of the app.

| Command | What it does |
| --- | --- |
| nextmail subscriptions list | List the senders of newsletters and mailing lists, one row per sender and the address it writes to. `--status unsubscribed` lists the ones you left, and `--sort` orders them by `recent`, `most`, `unread` or `name` |
| nextmail subscriptions list-domains | The same, one row per sender domain |
| nextmail subscriptions unsubscribe <id> | Unsubscribe from a sender the way it asks for. `--bin` also moves its conversations to the Bin. Asks you to confirm |
| nextmail subscriptions unsubscribe-domain <domain> | Unsubscribe from every sender on a domain that takes a one-click request or an email. Asks you to confirm |
| nextmail subscriptions move <id> --destination <value> | Move what a sender sent to `archive`, `bin` or a label with `--label-id`, and keep moving its new mail with a rule unless you pass `--no-future` |
| nextmail senders get <email> | Read what is known about the organisation behind a sender |
| nextmail senders research <email> | Search the web for the organisation behind a sender and keep what was found |
| nextmail analytics mailbox | Read how mail arrives over a window: per day and per hour, by address and by sender, and what waits on a reply |
| nextmail analytics sending | Read what became of the mail the workspace sent: delivered, failed, bounced and complained about, per day |

- `subscriptions list`, `list-domains`, `senders get`, `senders research` and `analytics mailbox` need `threads:read`, and `analytics sending` needs `emails:read`. Unsubscribing and moving need `threads:write`, and the rule `subscriptions move` makes needs `rules:write` too.
- A sender that only offers an unsubscribe page answers with `method` `link` and the page in `url`, which a person has to open.
- `subscriptions unsubscribe`, `unsubscribe-domain` and `threads unsubscribe` ask you to confirm, since an unsubscribe cannot be taken back. Unattended they stop with exit code `2` unless you pass `--yes`.
- Only the workspace owner may run `senders research`, with a key or a sign-in not limited to particular addresses or domains, and a workspace looks up at most 200 new domains a day.

## Reminders

`nextmail follow-ups` holds the reminders set on mail you sent, the ones “Remind me if nobody replies” makes in the app. If no real reply has arrived by the time of a reminder, its thread returns to the top of the inbox, unread.

| Command | What it does |
| --- | --- |
| nextmail follow-ups list | List your reminders, newest first. `--status` keeps `waiting`, `answered`, `due` or `cancelled`, and `--thread-id` keeps one thread |
| nextmail follow-ups set --thread-id <value> --remind-at <when> | Set a reminder on a thread, or move the one already waiting on it |
| nextmail follow-ups cancel <id> | Cancel a waiting reminder. Asks you to confirm |

- `--remind-at` takes a future ISO 8601 instant with `Z` or an offset, at most 365 days out, and anything else is 422 `invalid_follow_up`. `nextmail emails send --follow-up-at` sets a reminder as the mail goes out, and also takes a duration such as `P3D`.
- A real reply closes a reminder by itself, and automatic replies, delivery reports and mail from your own sending address do not count as one. With an API key the commands act on the reminders of the workspace owner, and after a browser sign-in on your own.
- `nextmail threads list --query "is:waiting in:anywhere"` finds, in every folder, the threads whose reminder nobody has answered yet.
