---
title: "Emails"
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/emails"
area: "API"
category: "Reference"
---

# Emails

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

## Operations

Send, schedule, cancel and retrieve.

### `POST /emails`

Send an email

Sends now, or schedules with `scheduledAt`. Answers 200 when the message has already gone and 202 when something still has to happen to it, so a caller may branch on the status code. Honours `Idempotency-Key`.

The body can come from `html`, `text`, a stored `template`, or an existing `draftId`: one of the four, never two.

Add `translate` to send it in the recipient's language rather than yours. It is resolved at accept time, before any record of the message exists, so a scheduled send carries the words that were approved and a translation that could not be produced refuses the send rather than delivering the original. Works with `template`, which is the useful case: the rendered output is what gets translated. See the Languages section, and `POST /emails/translate` to show somebody the result first.

- Scopes: `emails:send`.
- Honours `Idempotency-Key`.

**Headers**

- `Idempotency-Key` (`string`, up to 255 characters, pattern `^[A-Za-z0-9_.:-]+$`): Makes a retry safe. Reusing one with a different body is a 422.

**Request body**

- `from` (`string | object`, required): Sender as `billing@acme.com`, `Acme Billing <billing@acme.com>` or `{ email, name }`. It must be an address the key may send as, otherwise 403 `from_address_forbidden`.
  - `email` (`string`, required, up to 320 characters)
  - `name` (`string`, up to 128 characters)
- `to` (`(string | object)[]`, required, 1 to 50 items): One recipient or a list. `to`, `cc` and `bcc` together hold at most 50 addresses, and more is a 422 `too_many_recipients`.
  - `email` (`string`, required, up to 320 characters)
  - `name` (`string`, up to 128 characters)
- `cc` (`(string | object)[]`, up to 50 items, default `[]`): Copy recipients, counted toward the 50 recipient ceiling.
  - `email` (`string`, required, up to 320 characters)
  - `name` (`string`, up to 128 characters)
- `bcc` (`(string | object)[]`, up to 50 items, default `[]`): Blind copy recipients, counted toward the 50 recipient ceiling.
  - `email` (`string`, required, up to 320 characters)
  - `name` (`string`, up to 128 characters)
- `replyTo` (`string | object`): Written into the `Reply-To` header.
  - `email` (`string`, required, up to 320 characters)
  - `name` (`string`, up to 128 characters)
- `subject` (`string`, up to 998 characters, default `""`): At most 998 characters. Falls back to the template or draft subject when empty.
- `html` (`string`, up to 1000000 characters): HTML body, at most 1,000,000 characters.
- `text` (`string`, up to 1000000 characters): Plain text body, at most 1,000,000 characters.
- `template` (`object`): A stored template by id or slug. Omitting `version` resolves whatever is published at that moment, so pin it when somebody else owns the copy.
  - `id` (`string`, required, 1 to 128 characters)
  - `version` (`integer`, more than 0, at most 100000)
  - `props` (`Record<string, any>`)
  - `slots` (`Record<string, any>`)
- `translate` (`object`): `{ to, from?, includeOriginal?, subject? }`. `to` takes a code, an English name or an endonym. `includeOriginal` and `subject` both default to true.
  - `to` (`string`, required, 2 to 60 characters)
  - `from` (`string`, 2 to 60 characters)
  - `includeOriginal` (`boolean`, default `true`)
  - `subject` (`boolean`, default `true`)
- `headers` (`Record<string, string>`, default `{}`): Custom headers limited to `X-*`, `List-*`, `Reply-To`, `Precedence`, `Auto-Submitted`, `Importance`, `Priority` and `Feedback-ID`. Anything the server sets itself is a 422 `reserved_header`.
- `attachments` (`object[]`, up to 20 items, default `[]`): Up to 20 files, in three forms. `{ filename, content, contentType? }` carries the file as base64, up to 10 MB for all of them together. `{ url, filename?, contentType? }` names a file at an https address, which is fetched once when the message is accepted, within 15 seconds and the 25 MB the message has room for; the name and type default to the last part of the path and the type the server answered with, and an address that cannot be fetched is a 422 `attachment_unreachable`. `{ fileId }` names a file uploaded with `POST /files`, up to 100 MB. A file given as base64 or by `url` is kept with the message until it goes out, so it is never fetched twice and a retry of the send carries the same bytes.
  - `filename` (`string`, required, 1 to 255 characters)
  - `content` (`string`, required, 1 to 13981022 characters, pattern `^[A-Za-z0-9+/=\r\n]+$`)
  - `contentType` (`string`, up to 255 characters, pattern `^[\w.+-]+\/[\w.+-]+(?:[ \t]*;[ \t]*[\w.+-]+=(?:"[^"\r\n]*"|[\w.+-]+))*$`)
- `attachmentDelivery` (`string`, one of `"mime"`, `"link"`, `"auto"`): How the files in `attachments` travel. `mime` carries them inside the message, the way mail always has, so files that come to more than 25 MB together are refused. `link` uploads each file and puts a download link in the body in its place, so the message itself stays small. `auto` links when the files together come to more than 25 MB, or to more than 2 MB when the `from` domain has an active files domain, and carries them inside the message otherwise. Left out, the sender's mailbox setting applies, and that defaults to `auto`. A download link uses the files domain when the `from` domain has one active and the default Nextmail host otherwise.
- `threadId` (`string`, up to 256 characters): Files the sent message into an existing thread.
- `draftId` (`string`, up to 256 characters): Sends an existing draft as written. Cannot be combined with `template` or `translate`.
- `scheduledAt` (`string`, 3 to 64 characters): A `Date`, an ISO 8601 instant or a duration such as `PT1H` or `P2D`. At least one second and at most 365 days out.
- `followUpAt` (`string`, 3 to 64 characters): Asks to be reminded if nobody replies: an ISO 8601 instant, or a duration from now such as `P3D`, later than the time the message goes out and at most 365 days out. If no real reply has arrived by then, the thread returns to the top of the inbox, unread, with a push. `GET /follow-ups` lists the reminders and `DELETE /follow-ups/{id}` cancels one.
- `cancellableForSeconds` (`integer`, at least 0, at most 900, default `0`): An undo window from 0 to 900 seconds on an immediate send. Refused alongside `scheduledAt`, which is already cancellable until it goes.
- `tracking` (`object`): Open and link tracking for this send alone. A field left out takes the `from` address's own setting, then its domain's catch-all's when the catch-all caught that address rather than it being one you created, and is off when neither sets it. Set them per address with `PATCH /settings?address=`.
  - `opens` (`boolean`)
  - `clicks` (`boolean`)
- `signature` (`boolean`): An `html` body goes out exactly as written, so it carries a signature only when this is `true`, while a `text`-only body carries one unless this is `false`. When it is added it is the default signature of the `from` address: the one it keeps for replies when the message answers a thread (`threadId` or an `In-Reply-To` header), and the one for new mail otherwise. An address with no signature of its own takes its domain catch-all's when the catch-all caught that address, else the Nextmail footer unless that address turned the footer off. `signatureId` picks another signature of the address. Template sends and encrypted sends never carry one.
- `signatureId` (`string`, 1 to 64 characters): Adds one particular signature of the `from` address in place of its default, by the id `GET /signatures` returns. It signs an `html` body too, so `signature: true` is not needed with it, and it cannot be combined with `signature: false`. An id that is not a signature of the `from` address is a 422 `invalid_parameter`. When the signature is deleted before a scheduled message goes out, the message carries the default of the address instead.
- `stream` (`string`, one of `"transactional"`, `"broadcast"`): The lane the message travels in. `transactional`, the default, is for mail one person is waiting for, such as a password reset, a receipt or a code. `broadcast` is for newsletters and other mail to many people at once. Each lane has its own queue and its own reputation, so a newsletter never holds up or harms app mail, and the broadcast lane pauses itself when too many of its messages bounce or are reported as spam. A message sent to a paused lane is a 409 `stream_paused`. On the broadcast lane, a message to one recipient that sets no `List-Unsubscribe` header of its own carries `List-Unsubscribe` and `List-Unsubscribe-Post` for one-click unsubscribe. Whoever uses it joins the unsubscribe list (`GET /suppressions/unsubscribes`), and broadcast lane mail is not sent to anybody on that list: the recipient is marked `suppressed` and `email.suppressed` says why. Transactional mail still reaches them.
- `tags` (`Record<string, string>`, default `{}`): Up to 10 tags, keys of 1 to 64 letters, digits, `_` or `-`, values up to 256 characters. Echoed back on every read.

**Returns**

- `200` `Email`: Sent.
- `202` `Email`: Accepted: queued or scheduled.

**Errors**

- `409`: `domain_not_sendable`: the `from` domain is known to this workspace but its signing records are not in DNS yet, so nothing was accepted. Or `translation_not_configured`: this workspace has no AI configured, so `translate` cannot be honoured.
- `429`: `send_quota_exceeded`: this workspace has spent the monthly send allowance of its plan, which counts sends from this workspace alone. It resets on the first of the month. Or `ai_quota_exceeded`: with `translate`, this workspace has used today's AI actions. Nothing was sent, and it resets at midnight UTC. `rate_limited`: the key or app made more than 600 requests in 60 seconds. Wait for the seconds in `Retry-After`.
- `503`: The translator did not answer. Nothing was sent; the request is worth retrying.
- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.emails.send()`](https://nextmail.sh/docs/sdk/reference/emails#send); Python [`emails.send()`](https://nextmail.sh/docs/python/reference/emails#send); Ruby [`emails.send`](https://nextmail.sh/docs/ruby/reference/emails#send); PHP [`emails->send`](https://nextmail.sh/docs/php/reference/emails#send); Go [`Emails.Send`](https://nextmail.sh/docs/go/reference/emails#send); Java [`emails().send`](https://nextmail.sh/docs/java/reference/emails#send); C# [`Emails.SendAsync`](https://nextmail.sh/docs/csharp/reference/emails#send); CLI [`nextmail emails send`](https://nextmail.sh/docs/cli/reference/emails#emails-send); MCP [`replyToEmail`](https://nextmail.sh/docs/mcp/tools/writing#replyToEmail), [`sendEmail`](https://nextmail.sh/docs/mcp/tools/writing#sendEmail).

### `GET /emails`

List sent messages

Newest first, a page at a time. A key limited to particular addresses or domains lists only the messages sent from addresses it covers, and that filter runs before the page is cut, so every page but the last is full.

Requires the `emails:read` scope.

- Scopes: `emails:read`.

**Query parameters**

- `status` (`string`): One or more of the values an email's `status` takes, comma-separated. `bounced,partial` lists every message that did not reach everybody it went to.
- `from` (`string`): A bare sending address such as `billing@acme.com`, matched exactly and case insensitively. A display name form does not match.
- `broadcastId` (`string`, 1 to 64 characters): Only the copies of one broadcast, a `brd_` id from `POST /broadcasts`. Every person a broadcast reaches gets a message of their own, so this is the list of who it went to and what happened to each copy. An id that names no broadcast answers an empty page.
- `scheduledFrom` (`string`, format `date-time`): Only messages scheduled for this instant or later, ISO 8601 with a zone. With `scheduledTo` and `status=scheduled,queued` it lists what is waiting to go out in a window, as the calendar of the app does. A message with no `scheduledAt` is left out.
- `scheduledTo` (`string`, format `date-time`): Only messages scheduled for this instant or earlier. `scheduledFrom` after `scheduledTo` is a 422 `invalid_parameter`.
- `since` (`string`, format `date-time`): Only messages made at this instant or later, ISO 8601 with a zone.
- `until` (`string`, format `date-time`): Only messages made before this instant. `since` after `until` is a 422 `invalid_parameter`.
- `recipient` (`string`, up to 320 characters): Only messages that went to this address, as a to, cc or bcc recipient. The whole address, matched exactly.
- `tag` (`string`, up to 330 characters): Only messages sent with this tag: `name` for any value, or `name:value` for one value, as `tags` on `POST /emails` set them. Tags are stored encrypted, so the match is exact and case-sensitive.
- `limit` (`integer`, at least 1, at most 100, default `25`): Rows per page, a whole number from 1 to 100, defaulting to 25. Outside that range is a 422.
- `cursor` (`string`): A message id. Keyset, not offset.

**Returns**

- `200` `EmailList`: A page of messages, newest first.

**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.emails.list()`](https://nextmail.sh/docs/sdk/reference/emails#list), [`nextmail.emails.listAll()`](https://nextmail.sh/docs/sdk/reference/emails#listAll), [`nextmail.emails.iterate()`](https://nextmail.sh/docs/sdk/reference/emails#iterate); Python [`emails.list()`](https://nextmail.sh/docs/python/reference/emails#list), [`emails.list_all()`](https://nextmail.sh/docs/python/reference/emails#listAll), [`emails.iterate()`](https://nextmail.sh/docs/python/reference/emails#iterate); Ruby [`emails.list`](https://nextmail.sh/docs/ruby/reference/emails#list), [`emails.list_all`](https://nextmail.sh/docs/ruby/reference/emails#listAll), [`emails.iterate`](https://nextmail.sh/docs/ruby/reference/emails#iterate); PHP [`emails->list`](https://nextmail.sh/docs/php/reference/emails#list), [`emails->listAll`](https://nextmail.sh/docs/php/reference/emails#listAll), [`emails->iterate`](https://nextmail.sh/docs/php/reference/emails#iterate); Go [`Emails.List`](https://nextmail.sh/docs/go/reference/emails#list), [`Emails.ListAll`](https://nextmail.sh/docs/go/reference/emails#listAll), [`Emails.Iterate`](https://nextmail.sh/docs/go/reference/emails#iterate); Java [`emails().list`](https://nextmail.sh/docs/java/reference/emails#list), [`emails().listAll`](https://nextmail.sh/docs/java/reference/emails#listAll), [`emails().iterate`](https://nextmail.sh/docs/java/reference/emails#iterate); C# [`Emails.ListAsync`](https://nextmail.sh/docs/csharp/reference/emails#list), [`Emails.ListAllAsync`](https://nextmail.sh/docs/csharp/reference/emails#listAll), [`Emails.IterateAsync`](https://nextmail.sh/docs/csharp/reference/emails#iterate); CLI [`nextmail emails list`](https://nextmail.sh/docs/cli/reference/emails#emails-list); MCP [`listSentEmails`](https://nextmail.sh/docs/mcp/tools/sent#listSentEmails).

### `POST /emails/batch`

Send up to 100 messages

Per item, never all-or-nothing: a batch that rolled back on one bad address would make the caller's retry a question of which messages had already gone. Honours `Idempotency-Key`.

- Scopes: `emails:send`.
- Honours `Idempotency-Key`.

**Headers**

- `Idempotency-Key` (`string`, up to 255 characters, pattern `^[A-Za-z0-9_.:-]+$`): Makes a retry of the whole batch safe. Each message is keyed as the key, a slash and its index, so a retry with the same key replays the messages that already went, with `replayed: true`, and sends only the rest. Reusing it with a different message at an index refuses that item with `idempotency_key_reuse`.

**Request body**

- `emails` (`SendEmailRequest[]`, up to 100 items)

**Returns**

- `207` `BatchSendResult`: Per-item results, in the order the messages were sent.

**Errors**

- `429`: This workspace has spent the monthly send allowance of its plan, which counts sends from this workspace alone. It resets on the first of the month. `rate_limited`: the key or app made more than 600 requests in 60 seconds. Wait for the seconds in `Retry-After`.
- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.emails.sendBatch()`](https://nextmail.sh/docs/sdk/reference/emails#sendBatch); Python [`emails.send_batch()`](https://nextmail.sh/docs/python/reference/emails#sendBatch); Ruby [`emails.send_batch`](https://nextmail.sh/docs/ruby/reference/emails#sendBatch); PHP [`emails->sendBatch`](https://nextmail.sh/docs/php/reference/emails#sendBatch); Go [`Emails.SendBatch`](https://nextmail.sh/docs/go/reference/emails#sendBatch); Java [`emails().sendBatch`](https://nextmail.sh/docs/java/reference/emails#sendBatch); C# [`Emails.SendBatchAsync`](https://nextmail.sh/docs/csharp/reference/emails#sendBatch); CLI [`nextmail emails send-batch`](https://nextmail.sh/docs/cli/reference/emails#emails-send-batch).

### `POST /emails/translate`

Translate a message without sending it

The same round trip `translate` makes on a send, stopped one step early. The same function produces both, so what this shows is what would go out.

No scope of its own, deliberately. It grants nothing a sender could not already do, and a scope nobody can tell apart from `emails:send` on a consent screen makes every other scope on that list mean slightly less.

The body is capped at the same megabyte the send path allows, but translation itself refuses anything over 30,000 characters with `translation_too_long`. That is a refusal and not a truncation on purpose: half a translated message has no seam to show where it stopped, and the person reading acts on the half they were given.

Requires the `emails:send` scope.

- Scopes: `emails:send`.

**Request body**

- `html` (`string`, up to 1000000 characters): HTML body to translate. Only the content inside `<body>` is sent to the model when the markup is a full document.
- `text` (`string`, up to 1000000 characters): Plain text body to translate. Translated separately when given alongside `html`.
- `subject` (`string`, up to 998 characters): Subject line to translate, at most 998 characters.
- `to` (`string`, required, 2 to 60 characters): Target language as a code (`de`), English name (`German`) or endonym (`Deutsch`). An unrecognised value is a 422 `invalid_parameter` on `to`.
- `from` (`string`, 2 to 60 characters): The language you wrote in. Stating it skips the detection call.
- `includeOriginal` (`boolean`, default `true`): Defaults to true, placing your original text below the translation under a caption in the target language.

**Returns**

- `200` `Translation`: The translation. Nothing was sent.

**Errors**

- `409`: This workspace has no AI configured, so nothing can be translated.
- `429`: `ai_quota_exceeded`: this workspace has used today's AI actions. It resets at midnight UTC. `rate_limited`: the key or app made more than 600 requests in 60 seconds. Wait for the seconds in `Retry-After`.
- `503`: The translator did not answer. Nothing was sent; the request is worth retrying.
- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.emails.translate()`](https://nextmail.sh/docs/sdk/reference/emails#translate); Python [`emails.translate()`](https://nextmail.sh/docs/python/reference/emails#translate); Ruby [`emails.translate`](https://nextmail.sh/docs/ruby/reference/emails#translate); PHP [`emails->translate`](https://nextmail.sh/docs/php/reference/emails#translate); Go [`Emails.Translate`](https://nextmail.sh/docs/go/reference/emails#translate); Java [`emails().translate`](https://nextmail.sh/docs/java/reference/emails#translate); C# [`Emails.TranslateAsync`](https://nextmail.sh/docs/csharp/reference/emails#translate); CLI [`nextmail emails translate`](https://nextmail.sh/docs/cli/reference/emails#emails-translate); MCP [`previewTranslation`](https://nextmail.sh/docs/mcp/tools/writing#previewTranslation).

### `POST /emails/check`

Check how a message would be rated, without sending it

Scores a message the way a receiving mailbox would, before you send it: a spam score, a phishing score and an AI-writing score, each 0 to 100, with the signals behind them. Run it while someone writes, or before an automated send, and fix what it names.

The same checks score every message that arrives in a mailbox on Nextmail, so what you see here is what a recipient on Nextmail sees in Details. It cannot know a recipient's own filter, sender history or reputation, so a low score is a good sign and not a delivery guarantee.

No scope of its own, for the same reason as the translation preview: it grants nothing a sender could not already do. It spends no AI action and never calls a model.

Requires the `emails:send` scope.

- Scopes: `emails:send`.

**Request body**

- `from` (`string`, up to 320 characters): The address it will be sent from.
- `fromName` (`string`, up to 320 characters): The display name it will carry. A name that claims another address or a known brand raises the phishing score.
- `replyTo` (`string`, up to 320 characters): A Reply-To on a different domain raises the phishing score.
- `subject` (`string`, up to 998 characters, default `""`): Subject line, at most 998 characters.
- `html` (`string`, up to 200000 characters): HTML body. Links and images are read from it.
- `text` (`string`, up to 200000 characters): Plain text body. Taken from `html` when left out.
- `replying` (`boolean`): True when it answers an existing thread. A `Re:` subject on a message that answers nothing raises the spam score.
- `attachmentNames` (`string[]`, up to 100 items): File names, so an attachment that can run code is caught.

**Returns**

- `200` `EmailCheck`: The scores. Nothing was stored or sent.

**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.emails.check()`](https://nextmail.sh/docs/sdk/reference/emails#check); Python [`emails.check()`](https://nextmail.sh/docs/python/reference/emails#check); Ruby [`emails.check`](https://nextmail.sh/docs/ruby/reference/emails#check); PHP [`emails->check`](https://nextmail.sh/docs/php/reference/emails#check); Go [`Emails.Check`](https://nextmail.sh/docs/go/reference/emails#check); Java [`emails().check`](https://nextmail.sh/docs/java/reference/emails#check); C# [`Emails.CheckAsync`](https://nextmail.sh/docs/csharp/reference/emails#check); CLI [`nextmail emails check`](https://nextmail.sh/docs/cli/reference/emails#emails-check); MCP [`checkEmail`](https://nextmail.sh/docs/mcp/tools/writing#checkEmail).

### `GET /emails/{id}`

Retrieve a message

Requires the `emails:read` scope.

- Scopes: `emails:read`.

**Path parameters**

- `id` (`string`, required): The send id, `msg_` followed by 24 hex characters, as returned by `send`.

**Returns**

- `200` `Email`: The message, with per-recipient state.

**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.emails.get()`](https://nextmail.sh/docs/sdk/reference/emails#get); Python [`emails.get()`](https://nextmail.sh/docs/python/reference/emails#get); Ruby [`emails.get`](https://nextmail.sh/docs/ruby/reference/emails#get); PHP [`emails->get`](https://nextmail.sh/docs/php/reference/emails#get); Go [`Emails.Get`](https://nextmail.sh/docs/go/reference/emails#get); Java [`emails().get`](https://nextmail.sh/docs/java/reference/emails#get); C# [`Emails.GetAsync`](https://nextmail.sh/docs/csharp/reference/emails#get); CLI [`nextmail emails get`](https://nextmail.sh/docs/cli/reference/emails#emails-get); MCP [`getSentEmail`](https://nextmail.sh/docs/mcp/tools/sent#getSentEmail).

### `PATCH /emails/{id}`

Change a message that has not gone yet

Moves it with `scheduledAt`, and changes what it says or who it goes to with `subject`, `html`, `text`, `from`, `to`, `cc` and `bcc`, while it is still queued or scheduled. Send any of them together. A recipient list replaces the stored one whole, and `from` is checked as it is on a send, so it has to be an address the key may send as. A message that was translated when it was accepted keeps its wording, and one that was encrypted keeps its wording and its recipients: cancel it and send again instead.

- Scopes: `emails:send`.

**Path parameters**

- `id` (`string`, required): The `msg_` send id to move.

**Request body**

- `scheduledAt` (`string`, 3 to 64 characters): When it goes out instead: an ISO 8601 instant, or a duration such as `PT2H`, up to a year out.
- `subject` (`string`, up to 998 characters): The new subject, up to 998 characters.
- `html` (`string`, up to 1000000 characters): The new HTML body.
- `text` (`string`, up to 1000000 characters): The new plain text body.
- `from` (`string`, 3 to 320 characters): The address it goes out as, checked as it is on a send.
- `to` (`(string | object)[]`, 1 to 50 items): Replaces the stored recipients whole. So do `cc` and `bcc`.
  - `email` (`string`, required, up to 320 characters)
  - `name` (`string`, up to 128 characters)
- `cc` (`(string | object)[]`, up to 50 items): Replaces the copied recipients.
  - `email` (`string`, required, up to 320 characters)
  - `name` (`string`, up to 128 characters)
- `bcc` (`(string | object)[]`, up to 50 items): Replaces the blind copied recipients.
  - `email` (`string`, required, up to 320 characters)
  - `name` (`string`, up to 128 characters)

**Returns**

- `200` `Email`: The message as it is now.

**Errors**

- `409`: `email_not_cancellable`: it has already gone or was cancelled. `translation_locked`: it was translated when it was accepted, so its wording cannot change.
- 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.emails.reschedule()`](https://nextmail.sh/docs/sdk/reference/emails#reschedule), [`nextmail.emails.update()`](https://nextmail.sh/docs/sdk/reference/emails#update); Python [`emails.reschedule()`](https://nextmail.sh/docs/python/reference/emails#reschedule), [`emails.update()`](https://nextmail.sh/docs/python/reference/emails#update); Ruby [`emails.reschedule`](https://nextmail.sh/docs/ruby/reference/emails#reschedule), [`emails.update`](https://nextmail.sh/docs/ruby/reference/emails#update); PHP [`emails->reschedule`](https://nextmail.sh/docs/php/reference/emails#reschedule), [`emails->update`](https://nextmail.sh/docs/php/reference/emails#update); Go [`Emails.Reschedule`](https://nextmail.sh/docs/go/reference/emails#reschedule), [`Emails.Update`](https://nextmail.sh/docs/go/reference/emails#update); Java [`emails().reschedule`](https://nextmail.sh/docs/java/reference/emails#reschedule), [`emails().update`](https://nextmail.sh/docs/java/reference/emails#update); C# [`Emails.RescheduleAsync`](https://nextmail.sh/docs/csharp/reference/emails#reschedule), [`Emails.UpdateAsync`](https://nextmail.sh/docs/csharp/reference/emails#update); CLI [`nextmail emails reschedule`](https://nextmail.sh/docs/cli/reference/emails#emails-reschedule), [`nextmail emails update`](https://nextmail.sh/docs/cli/reference/emails#emails-update); MCP [`rescheduleEmail`](https://nextmail.sh/docs/mcp/tools/sent#rescheduleEmail), [`updateScheduledEmail`](https://nextmail.sh/docs/mcp/tools/sent#updateScheduledEmail).

### `POST /emails/{id}/cancel`

Cancel a message

Idempotent: cancelling twice returns the same cancelled message. A 409 means it has already gone and cannot be un-sent.

- Scopes: `emails:send`.

**Path parameters**

- `id` (`string`, required): The `msg_` send id to cancel.

**Returns**

- `200` `Email`: Cancelled.

**Errors**

- `409`: No longer cancellable.
- 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.emails.cancel()`](https://nextmail.sh/docs/sdk/reference/emails#cancel); Python [`emails.cancel()`](https://nextmail.sh/docs/python/reference/emails#cancel); Ruby [`emails.cancel`](https://nextmail.sh/docs/ruby/reference/emails#cancel); PHP [`emails->cancel`](https://nextmail.sh/docs/php/reference/emails#cancel); Go [`Emails.Cancel`](https://nextmail.sh/docs/go/reference/emails#cancel); Java [`emails().cancel`](https://nextmail.sh/docs/java/reference/emails#cancel); C# [`Emails.CancelAsync`](https://nextmail.sh/docs/csharp/reference/emails#cancel); CLI [`nextmail emails cancel`](https://nextmail.sh/docs/cli/reference/emails#emails-cancel); MCP [`cancelEmail`](https://nextmail.sh/docs/mcp/tools/sent#cancelEmail).

### `GET /emails/{id}/events`

What happened to a message

The event trail, oldest first, one page at a time. Nothing is dropped from it: a tracked message records an event for every counted open, click and download, so a widely read message runs to many pages, and following `nextCursor` while `hasMore` is true reaches the newest event.

Requires the `emails:read` scope.

- Scopes: `emails:read`.

**Path parameters**

- `id` (`string`, required): The `msg_` send id whose trail to read.

**Query parameters**

- `limit` (`integer`, at least 1, at most 100, default `25`): Rows per page, 1 to 100.
- `cursor` (`string`): An event id. Keyset, not offset: pass the previous page's `nextCursor`. One that names nothing in this list is a 400 `invalid_cursor`.

**Returns**

- `200` `EventList`: A page of the event trail, oldest first.

**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.emails.listEvents()`](https://nextmail.sh/docs/sdk/reference/emails#listEvents), [`nextmail.emails.listAllEvents()`](https://nextmail.sh/docs/sdk/reference/emails#listAllEvents), [`nextmail.emails.iterateEvents()`](https://nextmail.sh/docs/sdk/reference/emails#iterateEvents); Python [`emails.list_events()`](https://nextmail.sh/docs/python/reference/emails#listEvents), [`emails.list_all_events()`](https://nextmail.sh/docs/python/reference/emails#listAllEvents), [`emails.iterate_events()`](https://nextmail.sh/docs/python/reference/emails#iterateEvents); Ruby [`emails.list_events`](https://nextmail.sh/docs/ruby/reference/emails#listEvents), [`emails.list_all_events`](https://nextmail.sh/docs/ruby/reference/emails#listAllEvents), [`emails.iterate_events`](https://nextmail.sh/docs/ruby/reference/emails#iterateEvents); PHP [`emails->listEvents`](https://nextmail.sh/docs/php/reference/emails#listEvents), [`emails->listAllEvents`](https://nextmail.sh/docs/php/reference/emails#listAllEvents), [`emails->iterateEvents`](https://nextmail.sh/docs/php/reference/emails#iterateEvents); Go [`Emails.ListEvents`](https://nextmail.sh/docs/go/reference/emails#listEvents), [`Emails.ListAllEvents`](https://nextmail.sh/docs/go/reference/emails#listAllEvents), [`Emails.IterateEvents`](https://nextmail.sh/docs/go/reference/emails#iterateEvents); Java [`emails().listEvents`](https://nextmail.sh/docs/java/reference/emails#listEvents), [`emails().listAllEvents`](https://nextmail.sh/docs/java/reference/emails#listAllEvents), [`emails().iterateEvents`](https://nextmail.sh/docs/java/reference/emails#iterateEvents); C# [`Emails.ListEventsAsync`](https://nextmail.sh/docs/csharp/reference/emails#listEvents), [`Emails.ListAllEventsAsync`](https://nextmail.sh/docs/csharp/reference/emails#listAllEvents), [`Emails.IterateEventsAsync`](https://nextmail.sh/docs/csharp/reference/emails#iterateEvents); CLI [`nextmail emails list-events`](https://nextmail.sh/docs/cli/reference/emails#emails-list-events); MCP [`listEmailEvents`](https://nextmail.sh/docs/mcp/tools/sent#listEmailEvents).

### `POST /emails/compose`

Write an email with AI

Writes the body of an email from `prompt`, an instruction or a few rough notes, in the style of the mail this workspace has sent before, as the composer of the app does. Give `threadId` to write a reply: the messages of that thread are read as context, which also needs `threads:read`. Give `draft`, the body written so far, to have `prompt` change it instead, and `withSubject` to get a subject as well. Give `send`, the send options as they stand, to let `prompt` change them too, as the describe bar of the composer does: the recipients, when it sends, tracking, the signature, the language, the address it goes out as (give `from` and `send.fromOptions`) and end-to-end encryption (give `send.encrypt`). Give `templates: true` to let `prompt` switch it to one of the workspace's published templates, which needs `templates:read`, and `files: true` to let it attach files the workspace holds, which needs `files:read`. The answer then carries `send` with only what changed, in the shape `POST /emails` takes, and times are read in `timeZone` or the time zone of the account. Nothing is saved or sent: pass the result to `POST /emails` or `POST /drafts`. It spends one AI action.

Requires the `emails:send` scope.

- Scopes: `emails:send`.

**Request body**

- `prompt` (`string`, required, up to 20000 characters): What to write: an instruction, a rough draft or a few notes. With `draft`, what to change in it, and it may be empty to have the draft finished as it is.
- `subject` (`string`, up to 998 characters): The subject so far, if there is one.
- `from` (`string`, 1 to 320 characters): The address it goes out as, so it is written as that address.
- `template` (`string`, 1 to 128 characters): The id of the template the email uses now. Its body is then the template, so `prompt` leaves the body alone.
- `templates` (`boolean`): Let `prompt` switch the email to one of the workspace's published templates, which needs `templates:read`. The answer names it in `template`.
- `files` (`boolean`): Let `prompt` attach files the workspace holds, which needs `files:read`. The answer lists them in `attach`, each ready to send as `{ "fileId": id }`.
- `withSubject` (`boolean`): Also write a subject. The answer then carries `subject`: a new one when there was none or `prompt` asks for one, otherwise the one given.
- `draft` (`string`, up to 20000 characters): The body written so far, as text or Markdown. `prompt` then says what to change, and `body` comes back as the whole draft with that change made.
- `to` (`string[]`, up to 50 items): Who it goes to, so the greeting and tone fit.
- `cc` (`string[]`, up to 50 items): Who is copied.
- `bcc` (`string[]`, up to 50 items): Who is blind copied.
- `send` (`object`): The send options as they stand. Give it, even empty, to let `prompt` change them: the answer then carries `send` with what changed.
  - `scheduledAt` (`string`, nullable, up to 64 characters): When it is set to send, as an ISO-8601 time, or null when it sends right away.
  - `tracking` (`object`): Whether opens and clicks are tracked as it stands.
    - `opens` (`boolean`)
    - `clicks` (`boolean`)
  - `signature` (`boolean`): Whether the signature is added as it stands.
  - `translate` (`object`, nullable): The language it is translated into as it stands, or null when it is not.
    - `to` (`string`, required, 2 to 60 characters): A language code such as `de`, or a language name.
  - `fromOptions` (`string[]`, up to 100 items): The addresses it may go out as. `prompt` can then move it to one of them, and the answer names it in `send.from`.
  - `encrypt` (`boolean`): Whether it is encrypted end to end as it stands. Give it only when every recipient can receive encrypted mail, so `prompt` can turn encryption on or off.
- `timeZone` (`string`, 1 to 64 characters): The IANA time zone to read times in, such as `Europe/Berlin`. Without it, the time zone in the account settings.
- `threadId` (`string`, 1 to 200 characters): A thread to reply in. Its messages are read as context, and it needs `threads:read`.

**Returns**

- `200` `EmailComposition`: The body that was written.

**Errors**

- `409`: `ai_not_configured`: AI writing is not available on this server.
- `429`: `ai_quota_exceeded`: this workspace has used the AI actions of its plan for today. Nothing was written, and it resets at midnight UTC. `rate_limited`: the key or app made more than 600 requests in 60 seconds. Wait for the seconds in `Retry-After`.
- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.emails.compose()`](https://nextmail.sh/docs/sdk/reference/emails#compose); Python [`emails.compose()`](https://nextmail.sh/docs/python/reference/emails#compose); Ruby [`emails.compose`](https://nextmail.sh/docs/ruby/reference/emails#compose); PHP [`emails->compose`](https://nextmail.sh/docs/php/reference/emails#compose); Go [`Emails.Compose`](https://nextmail.sh/docs/go/reference/emails#compose); Java [`emails().compose`](https://nextmail.sh/docs/java/reference/emails#compose); C# [`Emails.ComposeAsync`](https://nextmail.sh/docs/csharp/reference/emails#compose); CLI [`nextmail emails compose`](https://nextmail.sh/docs/cli/reference/emails#emails-compose); MCP [`composeEmail`](https://nextmail.sh/docs/mcp/tools/writing#composeEmail).

### `POST /emails/rewrite`

Rewrite part of an email with AI

Rewrites a subject or a body and answers with up to five different versions, as the rewrite menu of the composer does. `action` is `shorten`, `lengthen`, `rephrase`, `formal`, `casual` or `custom`, which needs `instruction` to say what to change. Give `threadId` when the text is a reply, so the rewrite fits the conversation: its messages are read as context, which also needs `threads:read`. Nothing is saved. It spends one AI action.

Requires the `emails:send` scope.

- Scopes: `emails:send`.

**Request body**

- `target` (`string`, one of `"subject"`, `"body"`, default `"body"`): `body` or `subject`. Defaults to `body`.
- `text` (`string`, required, 1 to 100000 characters): The subject or body to rewrite.
- `action` (`string`, required, one of `"shorten"`, `"lengthen"`, `"rephrase"`, `"formal"`, `"casual"`, `"custom"`): What to do: `shorten`, `lengthen`, `rephrase`, `formal`, `casual` or `custom`.
- `instruction` (`string`, up to 500 characters): What to change, up to 500 characters. Required with `custom`.
- `count` (`integer`, at least 1, at most 5, default `3`): How many versions, 1 to 5. Defaults to 3.
- `threadId` (`string`, 1 to 200 characters): The thread the text replies in, read as context.

**Returns**

- `200` `EmailRewrite`: The versions, best first.

**Errors**

- `409`: `ai_not_configured`: AI writing is not available on this server.
- `429`: `ai_quota_exceeded`: this workspace has used the AI actions of its plan for today. Nothing was written, and it resets at midnight UTC. `rate_limited`: the key or app made more than 600 requests in 60 seconds. Wait for the seconds in `Retry-After`.
- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.emails.rewrite()`](https://nextmail.sh/docs/sdk/reference/emails#rewrite); Python [`emails.rewrite()`](https://nextmail.sh/docs/python/reference/emails#rewrite); Ruby [`emails.rewrite`](https://nextmail.sh/docs/ruby/reference/emails#rewrite); PHP [`emails->rewrite`](https://nextmail.sh/docs/php/reference/emails#rewrite); Go [`Emails.Rewrite`](https://nextmail.sh/docs/go/reference/emails#rewrite); Java [`emails().rewrite`](https://nextmail.sh/docs/java/reference/emails#rewrite); C# [`Emails.RewriteAsync`](https://nextmail.sh/docs/csharp/reference/emails#rewrite); CLI [`nextmail emails rewrite`](https://nextmail.sh/docs/cli/reference/emails#emails-rewrite); MCP [`rewriteEmail`](https://nextmail.sh/docs/mcp/tools/writing#rewriteEmail).

### `POST /emails/subject`

Suggest a subject line

Reads the body of an email and suggests a short subject for it, under 100 characters, in the style of the mail this workspace has sent. Nothing is saved. It spends one AI action.

Requires the `emails:send` scope.

- Scopes: `emails:send`.

**Request body**

- `message` (`string`, required, 1 to 100000 characters): The body of the email, as text or HTML.

**Returns**

- `200` `SubjectSuggestion`: The suggested subject.

**Errors**

- `409`: `ai_not_configured`: AI writing is not available on this server.
- `429`: `ai_quota_exceeded`: this workspace has used the AI actions of its plan for today. Nothing was written, and it resets at midnight UTC. `rate_limited`: the key or app made more than 600 requests in 60 seconds. Wait for the seconds in `Retry-After`.
- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `500`, described in the [error catalog](https://nextmail.sh/docs/api/errors).

Also available in: TypeScript [`nextmail.emails.suggestSubject()`](https://nextmail.sh/docs/sdk/reference/emails#suggestSubject); Python [`emails.suggest_subject()`](https://nextmail.sh/docs/python/reference/emails#suggestSubject); Ruby [`emails.suggest_subject`](https://nextmail.sh/docs/ruby/reference/emails#suggestSubject); PHP [`emails->suggestSubject`](https://nextmail.sh/docs/php/reference/emails#suggestSubject); Go [`Emails.SuggestSubject`](https://nextmail.sh/docs/go/reference/emails#suggestSubject); Java [`emails().suggestSubject`](https://nextmail.sh/docs/java/reference/emails#suggestSubject); C# [`Emails.SuggestSubjectAsync`](https://nextmail.sh/docs/csharp/reference/emails#suggestSubject); CLI [`nextmail emails suggest-subject`](https://nextmail.sh/docs/cli/reference/emails#emails-suggest-subject); MCP [`suggestSubject`](https://nextmail.sh/docs/mcp/tools/writing#suggestSubject).

### Objects

#### `BatchSendItem`

`object`

- `index` (`integer`, required, at least 0): Where the message sat in `emails`.
- `status` (`string`, required, one of `"ok"`, `"error"`)
- `email` (`object`): The message, when `status` is `ok`.
  - `object` (`string`, one of `"email"`)
  - `id` (`string`): The durable handle, `msg_` + 24 hex.
  - `status` (`string`, one of `"queued"`, `"scheduled"`, `"sending"`, `"sent"`, `"partial"`, `"bounced"`, `"cancelled"`, `"failed"`): Where the message is in its life. `sent` means it left: the sending service took it, which is not yet delivery, and a bounce reported afterwards still moves it on. `bounced` means every recipient it went to bounced, so nobody has it, and each entry in `recipients` carries the reason. `partial` means some recipients have it and some do not, whether the transport stopped part way or some of them bounced later; retrying is wrong, because the ones who have it cannot be un-sent.
  - `mode` (`string`, one of `"live"`, `"test"`)
  - `from` (`string`)
  - `subject` (`string`, nullable)
  - `messageId` (`string`, nullable): RFC 5322 Message-ID. Null until the MIME exists. Do NOT correlate on it: the header is rewritten on the way out, so the value here appears in no bounce or delivery report and a match on it never fires. A delivery event names the send by its `id`, as `emailId`.
  - `threadId` (`string`, nullable)
  - `transport` (`string`, nullable, one of `"ses"`, `"test"`, `"dev"`, `"connected"`): How the bytes left, once they have. Null until dispatch. `test` is what a message sent with an `oe_test_` key records: it was accepted and every recipient marked delivered, but nothing was carried. `dev` is not a way of sending either: it is what a message records where nothing is configured to carry mail, having been built and sent nowhere. `connected` means it left through the outgoing server of a connected mailbox, such as Gmail, and not through Nextmail.
  - `attempts` (`integer`)
  - `lastError` (`string`, nullable)
  - `scheduledAt` (`string`, nullable, format `date-time`)
  - `cancellableUntil` (`string`, nullable, format `date-time`)
  - `sentAt` (`string`, nullable, format `date-time`)
  - `tags` (`Record<string, string>`)
  - `broadcastId` (`string`, nullable): The `brd_` broadcast this message is one copy of, or null for a message sent on its own. `GET /emails?broadcastId=` lists every copy of one broadcast.
  - `source` (`string`, one of `"api"`, `"oauth"`, `"composer"`, `"mcp"`, `"ai"`, `"form"`, `"mail-app"`, `"booking"`)
  - `stream` (`string`, one of `"transactional"`, `"broadcast"`): The lane the message travels in: `transactional` for app mail, `broadcast` for newsletters and every copy of a broadcast.
  - `createdAt` (`string`, format `date-time`)
  - `recipients` (`object[]`): Returned on retrieval only.
    - `email` (`string`)
    - `name` (`string`, nullable)
    - `kind` (`string`, one of `"to"`, `"cc"`, `"bcc"`)
    - `status` (`string`, one of `"pending"`, `"delivered"`, `"failed"`, `"bounced"`, `"complained"`, `"suppressed"`, `"uncertain"`): `uncertain` is real and is shown as itself: a transport that failed part-way cannot say which recipients it reached, and calling those delivered or failed would both be guesses. `suppressed` is decided before dispatch rather than reported afterwards: the address bounced or complained in this workspace before, so this copy was never offered to the transport. A message whose recipients are all suppressed fails outright.
    - `error` (`string`, nullable)
    - `deliveredAt` (`string`, nullable, format `date-time`)
  - `translation` (`object`): Present only when the message was translated, and only on responses that carry the stored request, which are the send itself and a retrieval. A list row does not fetch it, so its absence there says nothing either way.
    - `language` (`string`): The resolved target code: `de`, `pt-BR`.
    - `languageName` (`string`): Its English name.
    - `detectedSourceLanguage` (`string`, nullable): Stated or detected. Null when detection abstained.
    - `subject` (`boolean`): Whether the subject was translated too.
    - `includeOriginal` (`boolean`): Whether the sender's own words went below the translation.
  - `tracking` (`Tracking`)
  - `replayed` (`boolean`): True when an `Idempotency-Key` replayed an earlier send of this item.
- `error` (`object`): Why the message was refused, when `status` is `error`: the fields of a whole-request error, without `requestId`.
  - `type` (`string`, required, one of `"invalid_request_error"`, `"authentication_error"`, `"permission_error"`, `"not_found_error"`, `"conflict_error"`, `"validation_error"`, `"rate_limit_error"`, `"api_error"`)
  - `code` (`string`, required)
  - `message` (`string`, required)
  - `param` (`string`): The offending field, dotted.

#### `BatchSendResult`

`object`

- `object` (`string`, required, one of `"list"`)
- `data` (`BatchSendItem[]`, required): One entry per message, in the order they were sent.
- `sent` (`integer`, required, at least 0): How many messages were accepted.
- `failed` (`integer`, required, at least 0): How many were refused. Their entries say why.

#### `Email`

`object`

- `object` (`string`, one of `"email"`)
- `id` (`string`): The durable handle, `msg_` + 24 hex.
- `status` (`string`, one of `"queued"`, `"scheduled"`, `"sending"`, `"sent"`, `"partial"`, `"bounced"`, `"cancelled"`, `"failed"`): Where the message is in its life. `sent` means it left: the sending service took it, which is not yet delivery, and a bounce reported afterwards still moves it on. `bounced` means every recipient it went to bounced, so nobody has it, and each entry in `recipients` carries the reason. `partial` means some recipients have it and some do not, whether the transport stopped part way or some of them bounced later; retrying is wrong, because the ones who have it cannot be un-sent.
- `mode` (`string`, one of `"live"`, `"test"`)
- `from` (`string`)
- `subject` (`string`, nullable)
- `messageId` (`string`, nullable): RFC 5322 Message-ID. Null until the MIME exists. Do NOT correlate on it: the header is rewritten on the way out, so the value here appears in no bounce or delivery report and a match on it never fires. A delivery event names the send by its `id`, as `emailId`.
- `threadId` (`string`, nullable)
- `transport` (`string`, nullable, one of `"ses"`, `"test"`, `"dev"`, `"connected"`): How the bytes left, once they have. Null until dispatch. `test` is what a message sent with an `oe_test_` key records: it was accepted and every recipient marked delivered, but nothing was carried. `dev` is not a way of sending either: it is what a message records where nothing is configured to carry mail, having been built and sent nowhere. `connected` means it left through the outgoing server of a connected mailbox, such as Gmail, and not through Nextmail.
- `attempts` (`integer`)
- `lastError` (`string`, nullable)
- `scheduledAt` (`string`, nullable, format `date-time`)
- `cancellableUntil` (`string`, nullable, format `date-time`)
- `sentAt` (`string`, nullable, format `date-time`)
- `tags` (`Record<string, string>`)
- `broadcastId` (`string`, nullable): The `brd_` broadcast this message is one copy of, or null for a message sent on its own. `GET /emails?broadcastId=` lists every copy of one broadcast.
- `source` (`string`, one of `"api"`, `"oauth"`, `"composer"`, `"mcp"`, `"ai"`, `"form"`, `"mail-app"`, `"booking"`)
- `stream` (`string`, one of `"transactional"`, `"broadcast"`): The lane the message travels in: `transactional` for app mail, `broadcast` for newsletters and every copy of a broadcast.
- `createdAt` (`string`, format `date-time`)
- `recipients` (`object[]`): Returned on retrieval only.
  - `email` (`string`)
  - `name` (`string`, nullable)
  - `kind` (`string`, one of `"to"`, `"cc"`, `"bcc"`)
  - `status` (`string`, one of `"pending"`, `"delivered"`, `"failed"`, `"bounced"`, `"complained"`, `"suppressed"`, `"uncertain"`): `uncertain` is real and is shown as itself: a transport that failed part-way cannot say which recipients it reached, and calling those delivered or failed would both be guesses. `suppressed` is decided before dispatch rather than reported afterwards: the address bounced or complained in this workspace before, so this copy was never offered to the transport. A message whose recipients are all suppressed fails outright.
  - `error` (`string`, nullable)
  - `deliveredAt` (`string`, nullable, format `date-time`)
- `translation` (`object`): Present only when the message was translated, and only on responses that carry the stored request, which are the send itself and a retrieval. A list row does not fetch it, so its absence there says nothing either way.
  - `language` (`string`): The resolved target code: `de`, `pt-BR`.
  - `languageName` (`string`): Its English name.
  - `detectedSourceLanguage` (`string`, nullable): Stated or detected. Null when detection abstained.
  - `subject` (`boolean`): Whether the subject was translated too.
  - `includeOriginal` (`boolean`): Whether the sender's own words went below the translation.
- `tracking` (`Tracking`)

#### `EmailCheck`

`object`

How a receiving mailbox would likely rate this message, scored from its own content before it is sent. Every score runs 0 to 100, and higher means more of the thing it names. Nothing is stored and nothing is sent.

- `object` (`string`, required, one of `"email_check"`)
- `spam` (`object`, required): Spam-like traits in the subject, wording and links: capitals, stacked exclamation marks, stock spam phrases, money or prize bait, link shorteners, an image with almost no text, and a `Re:` subject on a message that answers nothing.
  - `score` (`integer`, required, at least 0, at most 100)
  - `level` (`string`, required, one of `"low"`, `"medium"`, `"high"`): `medium` from 35, `high` from 60.
  - `signals` (`string[]`, required): What raised the score, heaviest first, as stable ids such as `spam-phrases` or `link-shortener`.
- `phishing` (`object`, required): What a phishing filter would object to: link text that names a different site, links to a bare IP address, pressure to verify or pay, an attachment that can run code, and a display name that claims another address or a known brand. Sender authentication is taken as passing, since the message will be signed for your domain.
  - `score` (`integer`, required, at least 0, at most 100)
  - `level` (`string`, required, one of `"clear"`, `"caution"`, `"danger"`): `caution` from 30, `danger` from 60.
  - `signals` (`string[]`, required)
  - `reasons` (`string[]`, required): One plain sentence per signal, heaviest first.
- `ai` (`object`, required): How much the wording reads as written by a language model, from habits such as stock phrasing, even sentence lengths and markdown. Quoted history and the signature are cut first. It is a score, not a probability, and nobody can prove who wrote a sentence.
  - `score` (`integer`, required, nullable, at least 0, at most 100): Null when the message was not judged; `skipped` says why.
  - `level` (`string`, required, one of `"unknown"`, `"unremarkable"`, `"possible"`, `"likely"`)
  - `signals` (`string[]`, required)
  - `reasons` (`string[]`, required)
  - `words` (`integer`, required): Words of your own prose that were read.
  - `skipped` (`string`, required, nullable, one of `"too-short"`, `"encrypted"`, `"bulk"`): `too-short` under 40 words. Null when it was scored.

#### `EmailComposition`

`object`

- `object` (`string`, required, one of `"composition"`)
- `body` (`string`, required): The body that was written, or with `draft` the whole draft after the change. Text, with Markdown only for links, bold and lists.
- `subject` (`string`): The subject, when you asked with `withSubject`.
- `send` (`object`): When you gave `send`: only the send options `prompt` changed, empty when it changed none. `null` takes an option away.
  - `to` (`string[]`): The whole To list after the change.
  - `cc` (`string[]`): The whole Cc list after the change.
  - `bcc` (`string[]`): The whole Bcc list after the change.
  - `scheduledAt` (`string`, nullable, format `date-time`): When to send it, or `null` to send it right away.
  - `tracking` (`object`): Whether to track opens and link clicks.
    - `opens` (`boolean`)
    - `clicks` (`boolean`)
  - `signature` (`boolean`): Whether to add the signature.
  - `translate` (`object`, nullable): The language to send it in, or `null` to send it untranslated.
    - `to` (`string`, required): A language code, such as `de`.
  - `from` (`string`): The address to send it from instead, one of `send.fromOptions`.
  - `encrypt` (`boolean`): Whether to encrypt it end to end, when you gave `send.encrypt`.
- `template` (`object`): The template to send it with instead, when you gave `templates: true` and `prompt` asked for one. Its body replaces `body`.
  - `id` (`string`, required)
  - `name` (`string`, required)
- `attach` (`object[]`): Files the workspace holds to attach, when you gave `files: true` and `prompt` asked for them. Send each as `{ "fileId": id }` in `attachments`.
  - `id` (`string`, required): The file, as `GET /files/{id}` reads it.
  - `filename` (`string`, required)
  - `contentType` (`string`, required)
  - `size` (`integer`, required): In bytes.
- `attachMissing` (`string[]`): What `prompt` asked to attach that matched no file the workspace holds, as `prompt` named it.
- `note` (`string`): A short sentence when part of `prompt` cannot be done this way, such as adding somebody whose address it was not given.
- `sources` (`object[]`): The knowledge base items the draft drew on, when it used any: pinned notes at the levels of the sending address, and the passages that matched the request.
  - `id` (`string`): The knowledge item, as `GET /knowledge/{id}` reads it.
  - `title` (`string`)
  - `kind` (`string`, one of `"note"`, `"file"`, `"link"`)
  - `scope` (`string`): The level it sits at: empty for the whole workspace, `@` and a domain, or an address.
  - `url` (`string`, nullable): The page a link item reads, or null.

#### `EmailList`

`object`

- `object` (`string`, one of `"list"`)
- `data` (`Email[]`)
- `hasMore` (`boolean`)
- `nextCursor` (`string`, nullable)

#### `EmailRewrite`

`object`

- `object` (`string`, required, one of `"rewrite"`)
- `target` (`string`, required, one of `"subject"`, `"body"`)
- `variations` (`string[]`, required): Different versions of the text, never the original itself.

#### `Event`

`object`

- `object` (`string`, one of `"event"`)
- `id` (`string`)
- `type` (`string`): Dotted, such as `email.accepted`, `email.sent`, `email.delivered`, `email.bounced` or `email.opened`.
- `data` (`object`): Whatever the event recorded. An empty object when it carries nothing.
- `createdAt` (`string`, format `date-time`)

#### `EventList`

`object`

- `object` (`string`, one of `"list"`)
- `data` (`Event[]`)
- `hasMore` (`boolean`): True when another page follows. Pass `nextCursor` back as `cursor` to read it.
- `nextCursor` (`string`, nullable): The id of the last row on this page, or null on the last page.

#### `Language`

`object`

- `code` (`string`, required): BCP-47. What every endpoint here accepts and returns.
- `label` (`string`, required): The English name: "Brazilian Portuguese".
- `native` (`string`, required): The endonym, in its own script. Show this first.
- `flag` (`string`, required): Two regional-indicator codepoints. A scanning aid beside the native name, never an identifier. Never show it on its own.
- `rtl` (`boolean`, required): Right-to-left. A translated body for one of these is wrapped in `dir="rtl"` before it is sent, because a client that inherits direction renders it backwards otherwise.

#### `SendEmailRequest`

`object`

- `from` (`string | object`, required)
  - `email` (`string`, required, up to 320 characters)
  - `name` (`string`, up to 128 characters)
- `to` (`(string | object)[]`, required, 1 to 50 items)
  - `email` (`string`, required, up to 320 characters)
  - `name` (`string`, up to 128 characters)
- `cc` (`(string | object)[]`, up to 50 items, default `[]`)
  - `email` (`string`, required, up to 320 characters)
  - `name` (`string`, up to 128 characters)
- `bcc` (`(string | object)[]`, up to 50 items, default `[]`)
  - `email` (`string`, required, up to 320 characters)
  - `name` (`string`, up to 128 characters)
- `replyTo` (`string | object`)
  - `email` (`string`, required, up to 320 characters)
  - `name` (`string`, up to 128 characters)
- `subject` (`string`, up to 998 characters, default `""`)
- `html` (`string`, up to 1000000 characters)
- `text` (`string`, up to 1000000 characters)
- `template` (`object`)
  - `id` (`string`, required, 1 to 128 characters)
  - `version` (`integer`, more than 0, at most 100000)
  - `props` (`Record<string, any>`)
  - `slots` (`Record<string, any>`)
- `translate` (`object`)
  - `to` (`string`, required, 2 to 60 characters)
  - `from` (`string`, 2 to 60 characters)
  - `includeOriginal` (`boolean`, default `true`)
  - `subject` (`boolean`, default `true`)
- `headers` (`Record<string, string>`, default `{}`)
- `attachments` (`object[]`, up to 20 items, default `[]`): Up to 20 files, in three forms. `{ filename, content, contentType? }` carries the file as base64, up to 10 MB for all of them together. `{ url, filename?, contentType? }` names a file at an https address, which is fetched once when the message is accepted, within 15 seconds and the 25 MB the message has room for; the name and type default to the last part of the path and the type the server answered with, and an address that cannot be fetched is a 422 `attachment_unreachable`. `{ fileId }` names a file uploaded with `POST /files`, up to 100 MB. A file given as base64 or by `url` is kept with the message until it goes out, so it is never fetched twice and a retry of the send carries the same bytes.
  - `filename` (`string`, required, 1 to 255 characters)
  - `content` (`string`, required, 1 to 13981022 characters, pattern `^[A-Za-z0-9+/=\r\n]+$`)
  - `contentType` (`string`, up to 255 characters, pattern `^[\w.+-]+\/[\w.+-]+(?:[ \t]*;[ \t]*[\w.+-]+=(?:"[^"\r\n]*"|[\w.+-]+))*$`)
- `attachmentDelivery` (`string`, one of `"mime"`, `"link"`, `"auto"`): How the files in `attachments` travel. `mime` carries them inside the message, the way mail always has, so files that come to more than 25 MB together are refused. `link` uploads each file and puts a download link in the body in its place, so the message itself stays small. `auto` links when the files together come to more than 25 MB, or to more than 2 MB when the `from` domain has an active files domain, and carries them inside the message otherwise. Left out, the sender's mailbox setting applies, and that defaults to `auto`. A download link uses the files domain when the `from` domain has one active and the default Nextmail host otherwise.
- `threadId` (`string`, up to 256 characters)
- `draftId` (`string`, up to 256 characters)
- `scheduledAt` (`string`, 3 to 64 characters)
- `followUpAt` (`string`, 3 to 64 characters): Asks to be reminded if nobody replies: an ISO 8601 instant, or a duration from now such as `P3D`, later than the time the message goes out and at most 365 days out. If no real reply has arrived by then, the thread returns to the top of the inbox, unread, with a push. `GET /follow-ups` lists the reminders and `DELETE /follow-ups/{id}` cancels one.
- `cancellableForSeconds` (`integer`, at least 0, at most 900, default `0`)
- `tracking` (`object`): Open and link tracking for this send alone. A field left out takes the `from` address's own setting, then its domain's catch-all's when the catch-all caught that address rather than it being one you created, and is off when neither sets it. Set them per address with `PATCH /settings?address=`.
  - `opens` (`boolean`)
  - `clicks` (`boolean`)
- `signature` (`boolean`): An `html` body goes out exactly as written, so it carries a signature only when this is `true`, while a `text`-only body carries one unless this is `false`. When it is added it is the default signature of the `from` address: the one it keeps for replies when the message answers a thread (`threadId` or an `In-Reply-To` header), and the one for new mail otherwise. An address with no signature of its own takes its domain catch-all's when the catch-all caught that address, else the Nextmail footer unless that address turned the footer off. `signatureId` picks another signature of the address. Template sends and encrypted sends never carry one.
- `signatureId` (`string`, 1 to 64 characters): Adds one particular signature of the `from` address in place of its default, by the id `GET /signatures` returns. It signs an `html` body too, so `signature: true` is not needed with it, and it cannot be combined with `signature: false`. An id that is not a signature of the `from` address is a 422 `invalid_parameter`. When the signature is deleted before a scheduled message goes out, the message carries the default of the address instead.
- `stream` (`string`, one of `"transactional"`, `"broadcast"`): The lane the message travels in. `transactional`, the default, is for mail one person is waiting for, such as a password reset, a receipt or a code. `broadcast` is for newsletters and other mail to many people at once. Each lane has its own queue and its own reputation, so a newsletter never holds up or harms app mail, and the broadcast lane pauses itself when too many of its messages bounce or are reported as spam. A message sent to a paused lane is a 409 `stream_paused`. On the broadcast lane, a message to one recipient that sets no `List-Unsubscribe` header of its own carries `List-Unsubscribe` and `List-Unsubscribe-Post` for one-click unsubscribe. Whoever uses it joins the unsubscribe list (`GET /suppressions/unsubscribes`), and broadcast lane mail is not sent to anybody on that list: the recipient is marked `suppressed` and `email.suppressed` says why. Transactional mail still reaches them.
- `tags` (`Record<string, string>`, default `{}`)

#### `SubjectSuggestion`

`object`

- `object` (`string`, required, one of `"subject_suggestion"`)
- `subject` (`string`, required)

#### `Tracking`

`object`

Every `/tracking` endpoint returns this whole, its list included. It arrives trimmed in exactly one place, under `tracking` on a `GET /emails` row, where it is the counts half only: `opens`, `clicks`, `opened`, `clicked`, `openCount`, `clickCount` and `firstOpenAt`. A page of fifty sends each carrying its recipients and its links is a report nobody asked to have expanded. The trimmed form has no `id` on it either, so `/emails/{id}/tracking` rather than `/tracking/{id}` is the way back to the rest of it.

- `object` (`string`, one of `"tracking"`): Present when the report is the whole response body. Absent under an email's `tracking` field, which is part of that email rather than a resource in its own right.
- `id` (`string`): The tracking record, `tmsg_` + 24 hex. Not the message id and not the send id.
- `sendId` (`string`, nullable): The `msg_` this went out as, when the send service handled it. Null for mail the mailbox agent sent on its own behalf, which is most composer, MCP and assistant traffic. Those messages do get a send record, but nothing links this tracking row to it. Tracking covers the mailbox rather than only the traffic that came through this API.
- `threadId` (`string`, nullable)
- `messageId` (`string`, nullable): RFC 5322 Message-ID. Not a correlation key. The header is rewritten on the way out. Correlate on `id`.
- `subject` (`string`, nullable)
- `from` (`string`)
- `source` (`string`, one of `"api"`, `"oauth"`, `"composer"`, `"mcp"`, `"ai"`, `"form"`, `"mail-app"`, `"booking"`)
- `sentAt` (`string`, nullable, format `date-time`)
- `opens` (`boolean`): What was APPLIED to this message, resolved when it was sent from the setting of the address it was sent from (its own, else its domain catch-all's, else off, while a broadcast copy is on unless the broadcast or its address turned it off) and any per-send override, not what is switched on now. Turning tracking on today does not make yesterday's mail start reporting, and a report that implied otherwise would read as "nobody opened it".
- `clicks` (`boolean`): As `opens`, for link rewriting. The two are independent switches.
- `opened` (`boolean`)
- `clicked` (`boolean`)
- `attributable` (`boolean`): Whether every reading on this message can be pinned to a named recipient. False as soon as an unattributed copy has activity of its own, which is what happens whenever one body went to the whole list rather than a separate one per person. This is the flag that decides whether "Bob has not opened it" is a sentence a client is entitled to write, or whether all it may say is that somebody did. `recipients` carries the same fact one row at a time, and one row at a time is where it gets missed.
- `openCount` (`integer`): Opens that looked like a person, with repeat fetches within thirty seconds collapsed. A preview pane redrawing is not a second reading. Through Gmail this is a floor and not a total: its proxy fetches the image once and caches it, so later readings never reach us.
- `clickCount` (`integer`): Counted clicks. Stronger evidence than an open, and worth weighting as such: images are blocked far more often than links go unfollowed, so a message with clicks and no opens was certainly read.
- `openCountRaw` (`integer`): Every open hit, the automated ones included. `openCountRaw - openCount` is everything that was filtered out: Apple Mail Privacy Protection and corporate link scanners, which fetch on delivery whether or not a person ever looks, and alongside them the repeat fetches collapsed by the thirty-second window. Both are recorded and neither is counted, because discarding them outright would leave a gap in the log that nothing could explain. Do not read the difference as a machine count on its own. A message reopened twice in a minute lands in it too.
- `clickCountRaw` (`integer`): As `openCountRaw`, for clicks.
- `firstOpenAt` (`string`, nullable, format `date-time`)
- `lastOpenAt` (`string`, nullable, format `date-time`)
- `firstClickAt` (`string`, nullable, format `date-time`)
- `lastClickAt` (`string`, nullable, format `date-time`)
- `recipients` (`object[]`): One entry per tracked copy, which is not always one entry per person. Absent only from the trimmed form on a `GET /emails` row; every `/tracking` response carries it, list included.
  - `email` (`string`, nullable): Null where the bytes could not be varied per person: an encrypted message, one too large to rebuild for each recipient, or a fallback carrier that takes the whole recipient list in a single call. The reading is real; which of the recipients did it is not knowable, and the only honest rendering is "someone on this message", never a name chosen out of the list.
  - `kind` (`string`, nullable, one of `"to"`, `"cc"`, `"bcc"`)
  - `attributed` (`boolean`): False on exactly the rows described above. Branch on this rather than on `email` being a string, and show nothing where it is false: attributing an unattributed open to a named recipient invents evidence about a specific person.
  - `openCount` (`integer`)
  - `clickCount` (`integer`)
  - `firstOpenAt` (`string`, nullable, format `date-time`)
  - `lastOpenAt` (`string`, nullable, format `date-time`)
  - `firstClickAt` (`string`, nullable, format `date-time`)
  - `lastClickAt` (`string`, nullable, format `date-time`)
- `links` (`object[]`): The rewritten links, in the order they appeared in the message. Only links in the new part of the body are here: the quoted history under a reply belongs to whoever wrote it, and routing their URLs through our redirector would both rewrite their message and record the recipient "clicking" something we did not put there. Repeated destinations share one entry, because a campaign page linked from a header image, a button and a footer is one question asked three times. Absent only from the trimmed form on a `GET /emails` row.
  - `id` (`string`)
  - `url` (`string`): Where it actually goes: the original href.
  - `label` (`string`, nullable): The text the link read as in the message, where it had any. A bare URL rarely tells the sender which of five links somebody followed.
  - `clickCount` (`integer`)
  - `clickCountRaw` (`integer`)

#### `Translation`

`object`

The preview, and exactly what a `translate` on `POST /emails` would produce for the same input. Show it, let someone edit it, then send the edited text as an ordinary `html`/`subject` with no `translate` on the request. Sending with `translate` after previewing translates a second time and discards the edits.

- `object` (`string`, one of `"translation"`)
- `language` (`Language`)
- `detectedSourceLanguage` (`Language`)
- `subject` (`string`, nullable): Null when no subject was given. Safe to put straight into a header.
- `html` (`string`, nullable): Null when no `html` was given. Carries `dir="rtl"` when the target needs it, and already contains the original beneath the translation when `includeOriginal` is on.
- `text` (`string`, nullable): Null when no `text` was given.
- `includeOriginal` (`boolean`): Echoed because it changes what `html` contains: with it on, the original is already in there and appending your own copy would send it twice.
