---
title: "Mail"
description: "Send with any body and attachments, read and search the mailbox, reply, file threads, and use disposable inboxes."
url: "https://openemail.uk/docs/cli/mail"
area: "CLI"
category: "Using it"
---

# Mail

Send with any body and attachments, read and search the mailbox, reply, file threads, and use disposable inboxes.

## Send

**Terminal**

```
openemail send --to ada@example.com --subject "Lunch?" --text "Thursday at noon works for me."
```

The body comes from the first of these that is there:

1. `--text` and `--html`, both together if you want both parts.
2. `--body-file <path>`. A `.html` or `.htm` file is sent as HTML and anything else as text, and `-` reads stdin.
3. Whatever is piped on stdin. Input that starts with an HTML tag is sent as HTML.
4. Your editor (`$VISUAL`, then `$EDITOR`), when you run it in a terminal.

Without `--from`, the CLI sends from the only address you can send from, or lets you pick one in a terminal. Unattended with more than one, it stops and asks for `--from`. In a terminal you see a summary and confirm before anything goes, which `--yes` skips.

| Flag | What it does |
| --- | --- |
| --to, --cc, --bcc | Recipients, comma separated or repeated. `Ada Lovelace <ada@example.com>` works too |
| --from, --reply-to | The sending address, and the Reply-To header |
| -s, --subject | The subject line |
| -a, --attach <path> | Attach a file. Repeat it for more |
| --at <when> | Send later: an ISO time such as `2026-10-01T09:00:00Z`, or a delay such as `10m`, `2h`, `1d` or `PT1H` |
| --undo <seconds> | Hold an immediate send for up to 900 seconds, so it can still be cancelled |
| --translate <language> | Translate the email into this language before it goes |
| --template <id>, --props <json> | Send a stored template with its props instead of a body |
| --draft <id> | Send a saved draft exactly as it was written |
| --thread <id> | File the sent email into this thread |
| --tag <key=value> | Tag the email. Repeat it for more |
| --idempotency-key <key> | Running the command again with the same key never sends twice |

**More sends**

```
cat report.md | openemail send --to team@acme.com --subject "Weekly report" --attach chart.png
openemail send --to ada@example.de --subject "Invoice" --body-file invoice.html --translate de --at 2h
openemail send --to ada@example.com --template welcome --props '{"name":"Ada"}' --tag campaign=onboarding
```

It prints the email id and its status. `sent`, `partial` and `failed` mean the send was tried within the request, and `queued` and `scheduled` mean it is held. A `failed` send exits with code `1`, and `--json` prints the whole email.

> Every send carries an idempotency key, yours or one the CLI makes up, so a retry after a dropped connection never sends the message twice. Pass your own when a pipeline may run the same step again.

## Attachments

`--attach` takes up to 20 files. While they come to 5 MB or less in total, they travel inside the message. When they come to more, each one is uploaded to Files first and the message refers to it, so a large file needs no extra step. The content type comes from the file extension.

## Inbox

**Terminal**

```
openemail inbox
openemail inbox --unread --limit 50
openemail inbox archive --sort oldest
openemail inbox --all --json | jq -r ".items[].id"
```

Lists one page of threads, newest first, with when each last moved, who wrote last, the subject, the labels and the thread id. A dot marks unread mail. The folder is the inbox unless you name another: `sent`, `archive`, `starred`, `snoozed`, `spam`, `trash`, `draft`, or a label id such as `USER_RECEIPTS`.

- `-u`, `--unread` keeps threads with unread mail, `-l`, `--label` keeps threads that carry a label, and `-q`, `--query` narrows the list with a search.
- `-n`, `--limit` sets the page size, from 1 to 100 (25 unless you say). When more remain, the next cursor is printed on stderr for `--cursor`, and `--all` reads every page.
- `--from-contacts` keeps threads whose newest message came from a saved contact, and `--sort` orders them by `newest`, `oldest`, `sender` or `subject`.
- `--json` prints `{ items, hasMore, nextCursor }`, with every thread whole.

## Search

**Terminal**

```
openemail search invoice from:ada
openemail search "quarterly report" has:pdf newer_than:30d
openemail search in:anywhere from:me to:ada --limit 50
openemail search -- invoice -from:ada
```

Search takes the syntax of the search box in the app: plain words, quoted phrases, and operators such as `from:`, `to:`, `subject:`, `has:pdf`, `is:unread`, `after:2026/01/31` and `newer_than:7d`. It searches the inbox, or the folder in `--folder`, unless the query names one with `in:` or `is:`, and `in:anywhere` searches every folder. It takes the flags of `inbox` and shows its results the same way.

> A word that starts with a dash is excluded from the results, and the CLI would read it as a flag, so put `--` before a query that has one.

- [Search](https://openemail.uk/docs/knowledge/reading/search.md): Every operator, and how a query is matched.

## Read a thread

**Terminal**

```
openemail read CAHk7pQ2x9LmZ4
openemail read CAHk7pQ2x9LmZ4 --message -1
openemail read CAHk7pQ2x9LmZ4 --html --message 1 > first.html
```

Prints every message on the thread, oldest first: who sent it and to whom, when, the subject, the attachments with their sizes and ids, and the body. A plain text part is shown as it is, and an HTML-only message is turned into readable text that keeps its lists, quotes, headings and links. Reading marks the thread read, unless you pass `--no-mark-read`.

- `-m`, `--message <n>` shows one message, counting from 1, or from the newest with a negative number.
- `--html` prints the raw HTML instead.
- An encrypted message is not decrypted here, so open it in the web app.

## Reply

**Terminal**

```
openemail reply CAHk7pQ2x9LmZ4 --text "Thanks, that works for me."
openemail reply CAHk7pQ2x9LmZ4 --all --attach notes.pdf
echo "Confirmed." | openemail reply CAHk7pQ2x9LmZ4 --yes
```

Replies in the same thread to whoever sent the last message, or to its Reply-To address. When the last message is yours, the reply goes to the people it went to. `--all` copies everyone else on that message, leaving out your own addresses. The subject gets `Re:` unless it has it already, and the reply goes from the address the thread was delivered to when you can send from it.

It takes the body, attachment, scheduling, translation, tag and idempotency flags of `send`, with `--from` and `--subject`, and `--to`, `--cc` and `--bcc` override the recipients.

## File threads

| Command | What it does |
| --- | --- |
| archive <thread-id...> | Out of the inbox, still searchable |
| unarchive <thread-id...> | Back into the inbox |
| trash <thread-id...> | To the Bin. It asks you to confirm, because no call takes a thread back out |
| star, unstar | Add or remove the star |
| mark read, mark unread | Mark threads read or unread |
| snooze <thread-id...> --until <when> | Hide threads until a time, or for a delay such as `3h` or `1w`. They come back within about an hour of it |
| unsnooze | Bring snoozed threads back now |
| label add, remove --label <id> | Put labels on threads or take them off. `openemail labels list` shows the ids |

Each takes several thread ids and reports on each one. The first failure sets the exit code, and `--json` prints `{ results, succeeded, failed }`. Ids from a search chain straight in:

**Terminal**

```
openemail inbox --query "newer_than:30d from:alerts" --json | jq -r ".items[].id" | xargs openemail archive
```

## Disposable inboxes

`openemail temp` makes short-lived addresses that need no account and no sign-in, which suits sign-up tests and one-time codes. Each inbox token is kept in `~/.openemail/temp-mail.json`, so later commands find an inbox by its id or address, or on their own when you have only one.

**Terminal**

```
ADDRESS=$(openemail temp new --ttl 15)
openemail temp watch --first --json | jq -r .snippet
openemail temp list
openemail temp read
openemail temp read tinb_4f2a tmail_91c3
openemail temp delete --yes
```

- `temp new` prints only the address on stdout. It lives for 60 minutes unless `--ttl` says otherwise, for up to 24 hours, and `--name` and `--domain` choose the address.
- `temp watch` prints what has arrived, then checks every 3 seconds until you stop it or the inbox expires. `--first` stops at the first message, and `--json` prints one JSON line per message.
- `temp read` lists the messages with a short preview, or prints one whole message.
- `temp delete` deletes the inbox and its mail at once. The address stays reserved for 7 days after its lease would have ended.
- `--inbox-token` reaches an inbox this CLI did not create, in `temp read`, `watch` and `delete`.
- The sender of mail in a disposable inbox is never verified.

- [Temporary addresses](https://openemail.uk/docs/knowledge/addresses/temp-address.md): How disposable inboxes work, and what they keep.
