# Message labels

Discover message tags, update them in batches, and combine required or excluded labels.

Source: https://cherami.to/docs/api/labels



Labels are ordinary, case-sensitive tags on individual received or sent messages. Guests and registered accounts can label their own mail. Agents sharing an account see the same labels. No separate label-creation request is needed.

Message lists and details return a `labels` array, empty when there are no labels. Received messages can be labeled in any processing state; sent copies can be labeled in any sending state. Labels are not sent to recipients or added to original MIME.

## Add or remove labels [#add-or-remove-labels]

Use the endpoint for the message copy you want to organize:

* `PATCH /v1/messages/{message_id}` for received mail.
* `PATCH /v1/sent/{message_id}` for a saved outgoing message.

Both require bearer authentication and `Content-Type: application/json`. The JSON body is limited to 20 KiB.

```json
{"add_labels":["receipts","needs-review"],"remove_labels":["handled"]}
```

| Field           | Contract                                                                          |
| --------------- | --------------------------------------------------------------------------------- |
| `add_labels`    | Optional array of at most 32 label strings to add. Existing labels are unchanged. |
| `remove_labels` | Optional array of at most 32 label strings to remove. Absent labels are ignored.  |

At least one array must contain a label. Unknown fields are rejected. Duplicate names within an array are ignored. A name cannot appear in both arrays after trimming.

Names must contain 1–128 UTF-8 bytes after trimming surrounding whitespace, with no control characters or malformed Unicode. Case is preserved: `Receipts` and `receipts` are different tags. Labels are a set; do not rely on their order.

A successful update returns `200` with the resulting labels:

```json
{"id":"22222222-2222-4222-8222-222222222222","labels":["needs-review","receipts"]}
```

Additions and removals apply together without replacing unrelated labels. Repeating the same update does not duplicate labels. Concurrent changes to different labels are preserved; opposite changes to the same label follow write order. Labels are not a work-claiming lock.

Invalid changes return `400 invalid_labels`. Missing, deleted, or other-account messages return `404`. Label updates do not require sending or deletion permission and consume no sending allowance.

## Update several messages [#update-several-messages]

Use `PATCH /v1/messages/labels` for received mail or `PATCH /v1/sent/labels` for outgoing copies. Authentication and label-change rules are the same as for individual updates. The body may contain up to 32 KiB of JSON:

```json
{"message_ids":["22222222-2222-4222-8222-222222222222","33333333-3333-4333-8333-333333333333"],"add_labels":["handled"],"remove_labels":["needs-review"]}
```

Supply 1–100 message IDs in `message_ids`; duplicate IDs are updated once. Only `message_ids`, `add_labels`, and `remove_labels` are accepted. The same additions and removals apply to every target. IDs can belong to different inboxes owned by the account, but received and sent copies must use their respective endpoint.

The request is atomic: if any target is missing, deleted, or inaccessible, the response is a generic `404` and no labels change. Invalid ID arrays return `400 invalid_message_ids`. Success returns `200` with one result per distinct ID in request order:

```json
{"messages":[{"id":"22222222-2222-4222-8222-222222222222","labels":["handled"]},{"id":"33333333-3333-4333-8333-333333333333","labels":["handled"]}]}
```

Select explicit IDs before updating; this endpoint does not accept a filter. Larger jobs require separate batches, each atomic on its own. If a response is lost, the whole batch may have applied. Inspect current labels before retrying when other agents may be changing the same tags.

## Filter messages [#filter-messages]

Received lists, received counts, and sent lists accept the same label filters:

| Parameter     | Matches                                                               |
| ------------- | --------------------------------------------------------------------- |
| `labels_all`  | Every listed label must be present.                                   |
| `labels_any`  | At least one listed label must be present.                            |
| `labels_none` | None of the listed labels may be present. Unlabeled messages qualify. |

Supplied groups combine with AND. Each group accepts up to 32 names; names are trimmed and deduplicated using the ordinary label rules. Matching is exact and case-sensitive, not substring matching. Contradictory conditions simply match no messages.

In HTTP, repeat the group parameter for each name and URL-encode each value. Do not use comma-separated lists: a comma is part of a label name.

```http
GET /v1/inboxes/INBOX_ID/messages?labels_all=project-x&labels_any=needs-reply&labels_any=waiting&labels_none=done
GET /v1/inboxes/INBOX_ID/messages/count?labels_none=handled
GET /v1/inboxes/INBOX_ID/sent?labels_all=project-x&labels_all=report
```

Connected tools `list_messages`, `count_messages`, and `list_sent` use arrays for the three groups. For one required tag, pass `labels_all: ["receipts"]`, or use `?labels_all=receipts` in HTTP. Empty arrays impose no condition; in HTTP, omit unused groups rather than supplying an empty value.

Follow `next_cursor` with the same inbox and normalized filters. Name order, duplicate names, and surrounding whitespace do not change a filter. Changing or removing a condition requires starting without a cursor. Labels can change between pages, so results are a live view rather than a snapshot.

## Discover labels [#discover-labels]

`GET /v1/inboxes/{inbox_id}/labels` lists names currently used on undeleted received and sent copies in an owned, undeleted inbox. It requires bearer authentication and is available to guests through connected tools as `list_labels`.

Optional `prefix` restricts names by a literal, case-sensitive prefix, trimmed using label-name rules. Empty or omitted means all names; supply it at most once. `limit` is 1–100, default 20. Results are ordered by name using case-sensitive binary order, not locale-specific collation. Continue with `cursor` and the same inbox and prefix.

```json
{"labels":[{"name":"receipts","received_count":12,"sent_count":2}],"next_cursor":null}
```

Counts describe messages, not conversations, and include all processing and sending states. A name disappears when no undeleted message uses it. There is no separate label registry or rename operation. Results and counts can change while you paginate.

## Label while sending [#label-while-sending]

[Message submission](https://cherami.to/docs/api/sending#submit-a-message) accepts optional `labels`, an array of at most 32 names using the same rules. They apply to the saved sent copy, including rejected or unknown attempts, not to replies or received copies.

Initial labels are part of the send's idempotency input. Name order and duplicates do not matter; omitted and empty arrays are equivalent. Retry with the original initial labels even if you later edit the saved message's tags. A replay returns the current labels without reapplying the originals.

## Label behavior [#label-behavior]

Names such as `read`, `unread`, `trash`, and `approved-to-send` are just tags. They do not mark mail as read, hide or delete messages, authorize sending, or trigger processing. Cherami does not automatically apply labels.

Threads do not own labels and cannot be filtered or updated by label. Individual messages returned within a conversation retain their own labels. A reply does not inherit its parent's labels.

[Organizing mail with labels](https://cherami.to/docs/guides/labels)
