---
title: "openemail emails"
description: "Every command in this namespace, with its arguments, flags and examples."
url: "https://openemail.uk/docs/cli/reference/emails"
area: "CLI"
category: "Reference"
---

# openemail emails

Every command in this namespace, with its arguments, flags and examples.

## Commands

### `openemail emails send`

Send, schedule or translate and send one email

```bash
openemail emails send --from <value> --to <a,b> [flags]
openemail emails send --data <json|@file|-> [flags]
```

Sends one message now, or holds it for later with `--scheduled-at` or an undo window with `--cancellable-for-seconds`. The body comes from exactly one source: `html` and or `text`, a stored `template`, or an existing `--draft-id`. Naming none is a 422, and `template` alongside `html`, `text` or `--draft-id` is refused. An immediate send is dispatched inside the request, so the promise usually settles on a `sent`, `partial` or `failed` message. A held send settles as `queued` or `scheduled`, so read `status` rather than treating a resolved promise as delivered mail.

Every call carries an `Idempotency-Key`. The SDK generates one per call and reuses it on that call's retries, and the API claims it against the key's own unique index before anything is dispatched, so a retried network failure replays the original message instead of sending a second one. Pass `--idempotency-key` to extend that across processes and restarts, deriving it from what made the send necessary rather than from a clock. A replay resolves with `replayed: true` and the stored message in its current state. The same key with a different body is a 422 `idempotency_key_reuse`.

Add `translate` to deliver the message in the recipient's language. The translation runs when the request is accepted, before any record exists, so a scheduled send carries the approved wording and a translation that cannot be produced refuses the whole send: nothing is ever delivered untranslated as a fallback. By default the subject is translated too and your original text is placed below the translation, captioned in the target language. It works with `template`, translating what the template rendered, and is refused alongside `--draft-id` because a draft goes as it was written.

- Scopes: `emails:send`.
- Needs a sign-in.

**Flags**

- `--from <value>`: 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`. Required, here or in `--data`.
- `--to <a,b>` (repeatable): One recipient or a list. `to`, `cc` and `bcc` together hold at most 50 addresses, and more is a 422 `too_many_recipients`. Required, here or in `--data`.
- `--cc <a,b>` (repeatable, default `[]`): Copy recipients, counted toward the 50 recipient ceiling.
- `--bcc <a,b>` (repeatable, default `[]`): Blind copy recipients, counted toward the 50 recipient ceiling.
- `--reply-to <value>`: Written into the `Reply-To` header.
- `--subject <value>` (default `""`): At most 998 characters. Falls back to the template or draft subject when empty.
- `--html <value>`: HTML body, at most 1,000,000 characters.
- `--text <value>`: Plain text body, at most 1,000,000 characters.
- `--template <json|@file|->`: 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. JSON shaped as `{ id: string, version?: number, props?: Record<string, unknown>, slots?: Record<string, unknown> }`, inline or from a file with @path.
- `--draft-id <value>`: Sends an existing draft as written. Cannot be combined with `template` or `translate`.
- `--thread-id <value>`: Files the sent message into an existing thread.
- `--headers <json|@file|->` (repeatable, 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 <json|@file|->` (default `[]`): At most 20 files. Each entry is either an inline file, with `filename` and `content` as bytes or a base64 string (bytes are encoded for you, and inline files are capped at 5 MB in total once decoded), or a stored file as `{ fileId }` naming a file already uploaded to the workspace, which is how a file larger than the inline cap is sent. JSON shaped as `Array<AttachmentInput>`, inline or from a file with @path.
- `--attachment-delivery <value>`: How the files in `attachments` travel. `mime` carries them inside the message, so a file over 5 MB is refused. `link` uploads each file and puts a download link in the body in its place, so the message itself stays small. `auto` links only when the `from` domain has an active files domain and the files together come to more than 2 MB, and attaches them otherwise, so nothing changes for a domain with no files domain set up. Left out, the sender's mailbox setting applies, and that defaults to `auto`.
- `--scheduled-at <when>`: A `Date`, an ISO 8601 instant or a duration such as `PT1H` or `P2D`. At least one second and at most 365 days out.
- `--cancellable-for-seconds <n>` (default `0`): An undo window from 0 to 900 seconds on an immediate send. Refused alongside `--scheduled-at`, which is already cancellable until it goes.
- `--tracking <json|@file|->`: Overrides the tracking setting for this send. A field left out takes the setting of the address it is sent from: its own, else its domain catch-all's when the catch-all caught that address, else off. JSON shaped as `{ opens?: boolean, clicks?: boolean }`, inline or from a file with @path.
- `--signature`: 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 signature of the address it is sent from: its own, else its domain catch-all's when the catch-all caught that address, else the OpenEmail footer unless that address turned the footer off. Template sends and encrypted sends never carry one.
- `--tags <json|@file|->` (repeatable, default `{}`): Up to 10 tags, keys of 1 to 64 letters, digits, `_` or `-`, values up to 256 characters. Echoed back on every read.
- `--translate <json|@file|->`: `{ to, from?, includeOriginal?, subject? }`. `to` takes a code, an English name or an endonym. `includeOriginal` and `subject` both default to true. JSON shaped as `SendTranslateOptions`, inline or from a file with @path.
- `--idempotency-key <value>`: Your own key, 1 to 255 characters of letters, digits, `_`, `.`, `:` or `-`. Anything else is a 400 `invalid_idempotency_key`.
- `--data <json|@file|->`: The whole `body` as JSON, inline, from a file with @path, or - for standard input. Flags override its keys.

**Examples**

The required values only

```bash
openemail emails send --from 'Acme Billing <billing@acme.com>' --to ada@example.de
```

With optional flags

```bash
openemail emails send --from 'Acme Billing <billing@acme.com>' --to ada@example.de --subject 'Your September invoice' --html '<p>The invoice is attached. Tell me if anything on it looks wrong.</p>' --idempotency-key invoice:inv_2026_09_4192
```

Read the whole body from a JSON file

```bash
openemail emails send --data @email.json
```

Also available in: API [`POST /emails`](https://openemail.uk/docs/api/reference/emails#post-emails); SDK [`emails.send()`](https://openemail.uk/docs/sdk/reference/emails#send).

### `openemail emails send-batch`

Send up to 100 independent emails in one request

```bash
openemail emails send-batch <emails> [flags]
```

Sends each message in order, as if `emails.send` had been called for it, and reports per item. It is never all or nothing: a bad address on item 7 fails item 7 and the rest still go, because a batch that rolled back would turn your retry into a guess about which messages had already been delivered. The promise resolves whenever the batch was processed, so check `failed` and each item's `status` rather than relying on a throw.

The batch shares one `Idempotency-Key`, generated once per call or supplied as `--idempotency-key`, and the server derives a separate key per item from it and the item's position. Retrying the same array replays the items that already went and sends only the ones that did not. Reordering the array between attempts changes which body each position's key is bound to, so an item that moved comes back as an `idempotency_key_reuse` error.

Apart from a key or scope failure or a server fault, only problems with the batch as a whole reject the promise with a 422: an empty array, more than 100 messages, or more than 10 messages carrying `translate`. Translation costs several model calls per message and they run one after another, so a larger translated batch would time out partway. Split it, or schedule the messages instead.

- Scopes: `emails:send`.
- Needs a sign-in.

**Arguments**

- `<emails>` (required): Between 1 and 100 messages, each shaped exactly like the body of `emails.send` and validated on its own.

**Flags**

- `--idempotency-key <value>`: Your own batch key, 1 to 255 characters of letters, digits, `_`, `.`, `:` or `-`.

**Examples**

The required values only

```bash
openemail emails send-batch ./emails.json
```

With optional flags

```bash
openemail emails send-batch ./emails.json --idempotency-key shipments:2026-09-15
```

Also available in: API [`POST /emails/batch`](https://openemail.uk/docs/api/reference/emails#post-emails-batch); SDK [`emails.sendBatch()`](https://openemail.uk/docs/sdk/reference/emails#sendBatch).

### `openemail emails translate`

Preview a translation without sending anything

```bash
openemail emails translate --to <value> [flags]
openemail emails translate --data <json|@file|-> [flags]
```

Runs the same translation `translate` performs on a send and stops one step early. The same function produces both, so what comes back is what would go out. Nothing is stored and nothing is sent. Use it when somebody should read the translated wording before it reaches a recipient.

Send the approved result as an ordinary `subject` and `html` on `emails.send` with no `translate` option. Passing `translate` again translates a second time, moving the wording off the version that was signed off and discarding any edits. When `--include-original` is on, `html` already contains your original text below the translation, so do not append your own copy.

At least one of `html`, `text` or `subject` is required. `to` accepts a BCP-47 code, an English name or the language's own name, and the response reports the code it settled on in `language.code`, which is the form worth storing. State `from` to skip language detection. Otherwise it is detected from the body, and a detector that cannot tell returns null in `detectedSourceLanguage` rather than guessing.

- Scopes: `emails:send`.
- Needs a sign-in.

**Flags**

- `--to <value>`: Target language as a code (`de`), English name (`German`) or endonym (`Deutsch`). An unrecognised value is a 422 `invalid_parameter` on `to`. Required, here or in `--data`.
- `--from <value>`: The language you wrote in. Stating it skips the detection call.
- `--include-original` (default `true`): Defaults to true, placing your original text below the translation under a caption in the target language.
- `--subject <value>`: Subject line to translate, at most 998 characters.
- `--html <value>`: HTML body to translate. Only the content inside `<body>` is sent to the model when the markup is a full document.
- `--text <value>`: Plain text body to translate. Translated separately when given alongside `html`.
- `--data <json|@file|->`: The whole `body` as JSON, inline, from a file with @path, or - for standard input. Flags override its keys.

**Examples**

The required values only

```bash
openemail emails translate --to ja
```

With optional flags

```bash
openemail emails translate --to ja --from 'Acme Billing <billing@acme.com>' --subject 'Your September invoice' --html '<p>The invoice is attached.</p>'
```

Read the whole body from a JSON file

```bash
openemail emails translate --data @email.json
```

Also available in: API [`POST /emails/translate`](https://openemail.uk/docs/api/reference/emails#post-emails-translate); SDK [`emails.translate()`](https://openemail.uk/docs/sdk/reference/emails#translate).

### `openemail emails check`

Check how a message would be rated, without sending it

```bash
openemail emails check [flags]
```

Scores a message the way a receiving mailbox would, before it goes out: a spam score, a phishing score and an AI-writing score, each 0 to 100, with the signals behind each one. Nothing is stored and nothing is sent.

The same checks score every message that arrives in an OpenEmail mailbox, so what comes back is what an OpenEmail recipient 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.

Run it before an automated send, or as someone writes, and fix what `signals` names. Sender authentication is taken as passing, since the message will be signed for your domain.

- Scopes: `emails:send`.
- Needs a sign-in.

**Flags**

- `--subject <value>` (default `""`): Subject line, at most 998 characters.
- `--html <value>`: HTML body. Links and images are read from it.
- `--text <value>`: Plain text body. Taken from `html` when left out.
- `--from <value>`: The address it will be sent from.
- `--from-name <value>`: The display name it will carry. A name that claims another address or a known brand raises the phishing score.
- `--reply-to <value>`: A Reply-To on a different domain raises the phishing score.
- `--replying`: True when it answers an existing thread. A `Re:` subject on a message that answers nothing raises the spam score.
- `--attachment-names <a,b>` (repeatable): File names, so an attachment that can run code is caught.
- `--data <json|@file|->`: The whole `body` as JSON, inline, from a file with @path, or - for standard input. Flags override its keys.

**Examples**

```bash
openemail emails check
```

With optional flags

```bash
openemail emails check --subject 'Your September invoice' --html '<p>The invoice is attached.</p>' --from billing@acme.com
```

Also available in: API [`POST /emails/check`](https://openemail.uk/docs/api/reference/emails#post-emails-check); SDK [`emails.check()`](https://openemail.uk/docs/sdk/reference/emails#check).

### `openemail emails list`

List one page of sent emails, newest first

```bash
openemail emails list [flags]
```

Returns one page of the workspace's send records, ordered newest first. Paging is keyset rather than offset: `nextCursor` is the id of the last message on the page, and passing it back as `cursor` continues strictly after it, so messages sent while you page never shift or repeat rows. `hasMore` is false on the last page and `nextCursor` is then null.

Filter with `status`, one value or an array that the SDK joins with commas, with `from`, which matches the sending address exactly and ignores case, with `--broadcast-id`, which keeps the copies of one broadcast, and with `--scheduled-from` and `--scheduled-to`, which keep the messages scheduled inside a window. An unrecognised status is a 422 `invalid_parameter` naming the offending values, and a cursor that names no message in the workspace is a 400 `invalid_cursor`.

Rows are the summary form. They never carry `recipients` or `translation`, whose absence on a row says nothing either way, and a tracked message carries only the counts half of `tracking`: `opens`, `clicks`, `opened`, `clicked`, `openCount`, `clickCount` and `firstOpenAt`. Call `get` for per-recipient delivery state and `getTracking` for the full engagement report.

Add `--all` to walk every page: a table on a terminal, one JSON object per line when piped or with `--ndjson`, and one `{ items, hasMore, nextCursor }` document with `--json`. `--max <n>` stops after that many items.

- Scopes: `emails:read`.
- Needs a sign-in.
- Aliases: `ls`.

**Flags**

- `--status <a,b>` (repeatable): One or more of `queued`, `scheduled`, `sending`, `sent`, `partial`, `cancelled` and `failed`.
- `--from <value>`: A bare sending address such as `billing@acme.com`, matched exactly and case insensitively. A display name form does not match.
- `--broadcast-id <value>`: Only the copies of one broadcast, a `brd_` id from `broadcasts.send`. Each person a broadcast reaches gets a message of their own, so this lists who it went to and what happened to each copy. An id that names no broadcast answers an empty page.
- `--scheduled-from <when>`: Only messages scheduled for this instant or later. With `--scheduled-to` 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.
- `--scheduled-to <when>`: Only messages scheduled for this instant or earlier. `--scheduled-from` after `--scheduled-to` is a 422 `invalid_parameter`.
- `--limit <n>` (default `25`): Rows per page, a whole number from 1 to 100, defaulting to 25. Outside that range is a 422.
- `--cursor <value>`: The `nextCursor` from the previous page, which is a message id.
- `--all`: Fetch every page and stream the items as they arrive.
- `--max <n>`: Stop after this many items. Implies `--all`.
- `--ndjson`: Print every item as one JSON object per line. Implies `--all`

**Examples**

```bash
openemail emails list
```

With optional flags

```bash
openemail emails list --status failed,partial --from billing@acme.com --limit 50
```

Walk every page and stop after 100 items

```bash
openemail emails list --all --max 100
```

One JSON object per line when piped

```bash
openemail emails list --all > emails.ndjson
```

Also available in: API [`GET /emails`](https://openemail.uk/docs/api/reference/emails#get-emails); SDK [`emails.list()`](https://openemail.uk/docs/sdk/reference/emails#list).

### `openemail emails get`

Read one sent email with per-recipient state

```bash
openemail emails get <id> [flags]
```

Returns the full send record for one message: its lifecycle `status`, delivery `attempts` and `lastError`, the schedule fields, and the two parts a list row leaves out. `recipients` has one entry per address with its own `status`, `error` and `deliveredAt`, and `translation` records what was done to a translated send.

A message that went out as a single call can still land differently per recipient. `status` on the message says how the send went as a whole, while each recipient moves on as delivery reports, bounces and complaints arrive. `uncertain` is a real recipient state: a transport that failed partway cannot say which recipients it reached. `suppressed` marks an address that previously bounced or complained in this workspace and was held back.

When the message was tracked, `tracking` carries the full engagement report with per-recipient and per-link detail. When it was not tracked, the field is absent rather than zeroed, because a message with no pixel has no evidence about whether anybody read it.

- Scopes: `emails:read`.
- Needs a sign-in.
- Aliases: `show`, `view`.

**Arguments**

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

**Examples**

```bash
openemail emails get msg_3f9a1c07d2b84e6a9c5b1f20
```

Print the raw JSON

```bash
openemail emails get msg_3f9a1c07d2b84e6a9c5b1f20 --json
```

Also available in: API [`GET /emails/{id}`](https://openemail.uk/docs/api/reference/emails#get-emails-id); SDK [`emails.get()`](https://openemail.uk/docs/sdk/reference/emails#get).

### `openemail emails list-events`

Read one page of the event trail of one sent email

```bash
openemail emails list-events <id> [flags]
```

Returns one page of everything recorded against one send, oldest first. It is the audit behind the current `status`: when the message was accepted or held, when it was rescheduled or cancelled, when it went out or failed, and every delivery report, bounce, complaint, open, click and file download attributed to it afterwards.

Each event has a dotted `type` and a `data` object whose shape depends on it. `email.accepted`, `email.queued` and `email.scheduled` open the trail with the source and recipient count. `email.sent` names the `transport` and `messageId`. `email.failed` carries the `error`. `email.bounced` and `email.complained` list the affected `recipients` and whether they were suppressed. `email.delivered`, `email.rescheduled`, `email.cancelled`, `email.opened`, `email.clicked` and `email.downloaded` follow as they happen. `email.downloaded` counts a person fetching a file that went out as a download link, never a scanner, and carries no `recipient`: the link is the same for everyone the message went to, so a download cannot be attributed. `data` is an empty object when an event carries nothing.

Webhooks deliver a subset of these same events as they occur, so this is where to look when a webhook was missed or never subscribed.

Add `--all` to walk every page: a table on a terminal, one JSON object per line when piped or with `--ndjson`, and one `{ items, hasMore, nextCursor }` document with `--json`. `--max <n>` stops after that many items.

- Scopes: `emails:read`.
- Needs a sign-in.

**Arguments**

- `<id>` (required): The `msg_` send id whose trail to read.

**Flags**

- `--limit <n>` (default `25`): Events per page, a whole number from 1 to 100, defaulting to 25.
- `--cursor <value>`: The `nextCursor` from the previous page, which is an event id. One that names no event of this send is a 400 `invalid_cursor`.
- `--all`: Fetch every page and stream the items as they arrive.
- `--max <n>`: Stop after this many items. Implies `--all`.
- `--ndjson`: Print every item as one JSON object per line. Implies `--all`

**Examples**

The required values only

```bash
openemail emails list-events msg_3f9a1c07d2b84e6a9c5b1f20
```

With optional flags

```bash
openemail emails list-events msg_3f9a1c07d2b84e6a9c5b1f20 --limit 100
```

Walk every page and stop after 100 items

```bash
openemail emails list-events msg_3f9a1c07d2b84e6a9c5b1f20 --all --max 100
```

One JSON object per line when piped

```bash
openemail emails list-events msg_3f9a1c07d2b84e6a9c5b1f20 --all > emails.ndjson
```

Also available in: API [`GET /emails/{id}/events`](https://openemail.uk/docs/api/reference/emails#get-emails-id-events); SDK [`emails.listEvents()`](https://openemail.uk/docs/sdk/reference/emails#listEvents).

### `openemail emails get-tracking`

Read the engagement report for a sent email

```bash
openemail emails get-tracking <id> [flags]
```

Returns the same document `tracking.get` serves, reached from the `msg_` id a sender already holds. It has the message totals, one entry per tracked copy under `recipients`, and every rewritten link with its clicks under `links`.

A message that was never tracked is a 404 here rather than an empty report. "We were not recording" and "nobody opened it" are different answers, and a client that renders them the same way makes a claim about a reader on no evidence. Tracking applies per send: `opens` and `clicks` record what was applied when the message went out, resolved from the setting of the address it was sent from (its own, else its domain catch-all's when the catch-all caught that address, else on) and any `tracking` override on the send, not what is switched on now.

Read every count as a floor. An open is inferred from a mail client fetching an image, so a reader whose client blocks images is never counted, and Gmail fetches the image once through its proxy and serves later views from cache. A click is stronger evidence than an open.

- Scopes: `emails:read`.
- Needs a sign-in.

**Arguments**

- `<id>` (required): The `msg_` send id returned by `send`.

**Examples**

```bash
openemail emails get-tracking msg_3f9a1c07d2b84e6a9c5b1f20
```

Print the raw JSON

```bash
openemail emails get-tracking msg_3f9a1c07d2b84e6a9c5b1f20 --json
```

Also available in: API [`GET /emails/{id}/tracking`](https://openemail.uk/docs/api/reference/tracking#get-emails-id-tracking); SDK [`emails.getTracking()`](https://openemail.uk/docs/sdk/reference/emails#getTracking).

### `openemail emails cancel`

Stop a queued or scheduled email before it goes

```bash
openemail emails cancel <id> [flags]
```

Cancels a message that has not been dispatched yet: a send held by `scheduledAt`, or an immediate send still inside its `cancellableForSeconds` undo window. The message moves to `cancelled`, nothing is delivered, and an `email.cancelled` event is recorded and sent to subscribed webhooks.

Only `queued` and `scheduled` messages can be cancelled. Once a message is `sending`, `sent`, `partial` or `failed` the call is a 409 `email_not_cancellable`, since there is no pending dispatch left to stop and mail that has gone cannot be recalled. An immediate send with no undo window is dispatched inside the `send` request, so by the time you hold its id it is usually past this point.

Cancelling is idempotent. Cancelling an already cancelled message resolves with the same cancelled message rather than an error, and the SDK retries the call after a network failure or a retryable status.

- Scopes: `emails:send`.
- Needs a sign-in.
- Asks you to confirm.

**Arguments**

- `<id>` (required): The `msg_` send id to cancel.

**Examples**

```bash
openemail emails cancel msg_3f9a1c07d2b84e6a9c5b1f20
```

Skip the confirmation, for scripts

```bash
openemail emails cancel msg_3f9a1c07d2b84e6a9c5b1f20 --yes
```

Also available in: API [`POST /emails/{id}/cancel`](https://openemail.uk/docs/api/reference/emails#post-emails-id-cancel); SDK [`emails.cancel()`](https://openemail.uk/docs/sdk/reference/emails#cancel).

### `openemail emails reschedule`

Move a queued or scheduled email to a new send time

```bash
openemail emails reschedule <id> <scheduled-at> [flags]
```

Changes when a message that has not gone yet will be dispatched. `scheduledAt` is the only thing this call can change, and the SDK sends nothing else: the body, recipients and any translation stay exactly as they were accepted.

The new time takes a `Date`, an ISO 8601 instant or a duration such as `PT30M` or `P2D`, measured from when the server receives the request. It must be at least one second in the future and at most 365 days out, otherwise the call is a 422 on `scheduledAt`, usually `invalid_parameter`. Earlier and later times are both allowed.

Only `queued` and `scheduled` messages can be moved. Anything already `sending`, `sent`, `partial`, `cancelled` or `failed` is a 409 `email_not_cancellable`. A queued message inside its undo window can be rescheduled too, which turns it into a `scheduled` send that stays cancellable until the new time.

- Scopes: `emails:send`.
- Needs a sign-in.

**Arguments**

- `<id>` (required): The `msg_` send id to move.
- `<scheduled-at>` (required): The new send time. A `Date` is serialised with `toISOString()`, and a string is passed through as an instant or a duration.

**Examples**

```bash
openemail emails reschedule msg_3f9a1c07d2b84e6a9c5b1f20 2026-10-01T00:00:00Z
```

Print the raw JSON

```bash
openemail emails reschedule msg_3f9a1c07d2b84e6a9c5b1f20 2026-10-01T00:00:00Z --json
```

Also available in: API [`PATCH /emails/{id}`](https://openemail.uk/docs/api/reference/emails#patch-emails-id); SDK [`emails.reschedule()`](https://openemail.uk/docs/sdk/reference/emails#reschedule).

### `openemail emails update`

Change an email that has not gone yet

```bash
openemail emails update <id> [flags]
```

Changes a `queued` or `scheduled` message before it is dispatched: when it goes with `--scheduled-at`, what it says with `subject`, `html` and `text`, the address it goes out as with `from`, and who it goes to with `to`, `cc` and `bcc`. Send any of them together, and a field you leave out keeps its value. This is what editing a scheduled message in the calendar of the app does.

A recipient list replaces the stored one whole, and takes a string such as `Ada <ada@example.com>`, an object with `email` and `name`, or an array of either. `from` is checked as it is on a send, so it has to be an address the key may send as, or the call is a 403 `from_address_forbidden`.

Only messages that have not gone can change. Anything already `sending`, `sent`, `partial`, `cancelled` or `failed` is a 409 `email_not_cancellable`. A message translated when it was accepted keeps its approved wording, so a new `subject`, `html` or `text` on it is a 409 `translation_locked`, and one that was encrypted before it was scheduled keeps its wording and its recipients. Cancel those and send again instead.

- Scopes: `emails:send`.
- Needs a sign-in.
- Aliases: `edit`.

**Arguments**

- `<id>` (required): The `msg_` send id to change.

**Flags**

- `--scheduled-at <when>`: A new send time: a `Date`, an ISO 8601 instant or a duration such as `PT2H`, in the future and at most 365 days out.
- `--subject <value>`: The new subject, up to 998 characters.
- `--html <value>`: The new HTML body.
- `--text <value>`: The new plain text body.
- `--from <value>`: The address it goes out as instead, one the key may send as.
- `--to <a,b>` (repeatable): Replaces the recipients, at least one and at most 50 across the three lists.
- `--cc <a,b>` (repeatable): Replaces the copied recipients.
- `--bcc <a,b>` (repeatable): Replaces the blind copied recipients.
- `--data <json|@file|->`: The whole `patch` as JSON, inline, from a file with @path, or - for standard input. Flags override its keys.

**Examples**

With optional flags

```bash
openemail emails update msg_3f9a1c07d2b84e6a9c5b1f20 --subject 'Your September invoice, corrected' --to ada@example.com,grace@example.com
```

Print the raw JSON

```bash
openemail emails update msg_3f9a1c07d2b84e6a9c5b1f20 --subject 'Your September invoice, corrected' --to ada@example.com,grace@example.com --json
```

Also available in: API [`PATCH /emails/{id}`](https://openemail.uk/docs/api/reference/emails#patch-emails-id); SDK [`emails.update()`](https://openemail.uk/docs/sdk/reference/emails#update).

### `openemail emails compose`

Write an email with AI

```bash
openemail emails compose --prompt <value> [flags]
openemail emails compose --data <json|@file|-> [flags]
```

Writes the body of an email from `prompt`, an instruction, a rough draft or a few notes, in the style of the mail this workspace has sent before, as the composer of the app does. `subject`, `to` and `cc` help the greeting and the tone fit.

Give `--thread-id` to write a reply: the messages of that thread are read as context, which also needs `threads:read`, and a key limited to particular addresses can only use a thread that arrived at them.

Nothing is saved or sent. Pass `body` to `emails.send` or `drafts.create` when it reads right. Each call spends one of the workspace's AI actions, and a workspace that has used them all for the day is refused with a 429 `ai_quota_exceeded`.

- Scopes: `emails:send`.
- Needs a sign-in.

**Flags**

- `--prompt <value>`: What to write, up to 20,000 characters. Required, here or in `--data`.
- `--subject <value>`: The subject so far, if there is one.
- `--to <a,b>` (repeatable): Who it goes to.
- `--cc <a,b>` (repeatable): Who is copied.
- `--thread-id <value>`: A thread to reply in, read as context.
- `--data <json|@file|->`: The whole `body` as JSON, inline, from a file with @path, or - for standard input. Flags override its keys.

**Examples**

The required values only

```bash
openemail emails compose --prompt 'Thank Ada for the signed contract and ask for the invoice by Friday.'
```

With optional flags

```bash
openemail emails compose --prompt 'Thank Ada for the signed contract and ask for the invoice by Friday.' --subject 'Thank you' --to ada@example.com
```

Read the whole body from a JSON file

```bash
openemail emails compose --data @email.json
```

Also available in: API [`POST /emails/compose`](https://openemail.uk/docs/api/reference/emails#post-emails-compose); SDK [`emails.compose()`](https://openemail.uk/docs/sdk/reference/emails#compose).

### `openemail emails rewrite`

Rewrite part of an email with AI

```bash
openemail emails rewrite --text <value> --action <value> [flags]
openemail emails rewrite --data <json|@file|-> [flags]
```

Rewrites a subject or a body and resolves different versions of it, as the rewrite menu of the composer does. `action` is `shorten`, `lengthen`, `rephrase`, `formal`, `casual` or `custom`, and `custom` needs `instruction` to say what to change, or the call is a 422 `invalid_parameter` on `instruction`. `target` is `body`, the default, or `subject`, and `count` asks for 1 to 5 versions, 3 by default.

Give `--thread-id` when the text is a reply, so the rewrite fits the conversation. That also needs `threads:read`. The versions never repeat the original and never add facts that are not in it.

Nothing is saved. Each call spends one of the workspace's AI actions.

- Scopes: `emails:send`.
- Needs a sign-in.

**Flags**

- `--text <value>`: The subject or body to rewrite. Required, here or in `--data`.
- `--action <value>`: What to do: `shorten`, `lengthen`, `rephrase`, `formal`, `casual` or `custom`. Required, here or in `--data`.
- `--target <value>` (default `"body"`): `body` or `subject`. Defaults to `body`.
- `--instruction <value>`: What to change, up to 500 characters. Required with `custom`.
- `--count <n>` (default `3`): How many versions, 1 to 5. Defaults to 3.
- `--thread-id <value>`: The thread the text replies in, read as context.
- `--data <json|@file|->`: The whole `body` as JSON, inline, from a file with @path, or - for standard input. Flags override its keys.

**Examples**

```bash
openemail emails rewrite --text 'Hey, just checking whether you had a chance to look at the contract?' --action formal
```

Read the whole body from a JSON file

```bash
openemail emails rewrite --data @email.json
```

Also available in: API [`POST /emails/rewrite`](https://openemail.uk/docs/api/reference/emails#post-emails-rewrite); SDK [`emails.rewrite()`](https://openemail.uk/docs/sdk/reference/emails#rewrite).

### `openemail emails suggest-subject`

Suggest a subject line

```bash
openemail emails suggest-subject --message <value> [flags]
openemail emails suggest-subject --data <json|@file|-> [flags]
```

Reads the body of an email and resolves a short subject for it, under 100 characters, in the style of the mail this workspace has sent, as the subject button of the composer does.

Nothing is saved. Each call spends one of the workspace's AI actions.

- Scopes: `emails:send`.
- Needs a sign-in.

**Flags**

- `--message <value>`: The body of the email, as text or HTML. Required, here or in `--data`.
- `--data <json|@file|->`: The whole `body` as JSON, inline, from a file with @path, or - for standard input. Flags override its keys.

**Examples**

```bash
openemail emails suggest-subject --message 'Thanks for the signed contract. Could you send the invoice by Friday?'
```

Read the whole body from a JSON file

```bash
openemail emails suggest-subject --data @email.json
```

Also available in: API [`POST /emails/subject`](https://openemail.uk/docs/api/reference/emails#post-emails-subject); SDK [`emails.suggestSubject()`](https://openemail.uk/docs/sdk/reference/emails#suggestSubject).
