---
title: "Mail workflow"
description: "The MachineMail CLI mail commands: send a message, read one with its trust verdict, wait for a message or a verification code, and tail events live."
canonical: "https://machinemail.dev/docs/cli/commands/mail"
---

# Mail workflow

> The MachineMail CLI mail commands: send a message, read one with its trust verdict, wait for a message or a verification code, and tail events live.

Source: https://machinemail.dev/docs/cli/commands/mail

Top-level commands for the jobs agents and people do most: send a message, read one with its trust verdict, wait for a message or a verification code, and follow events live.

| Command         | Summary                                                                    |
| --------------- | -------------------------------------------------------------------------- |
| [`send`](#send) | Send an email from an inbox                                                |
| [`read`](#read) | Show a message: headers, trust verdict and agent-safe text                 |
| [`wait`](#wait) | Wait for a message to arrive (and extract a code or magic link with --otp) |
| [`tail`](#tail) | Follow live events (messages, deliveries, inboxes) as they happen          |

Every command also takes the [global flags](https://machinemail.dev/docs/cli/commands#global-flags).

## send

Send an email from an inbox.

Sends through POST /v0/inboxes/\{inbox\_id}/messages/send. The body comes from --text, --html or --file (a .html/.htm file is sent as HTML, anything else as text); pass - to any of them to read stdin. Stdin is never read unless you ask. --from defaults to MACHINEMAIL\_INBOX, the profile inbox from `machinemail register`, or the inbox of an inbox-scoped key.

```bash
machinemail send [flags]
```

#### Flags

| Field | Type | | Description |
| --- | --- | --- | --- |
| `--from <inbox-id>` | `string` |  | Inbox to send from. |
| `--to <address>` | `string[]` |  | Recipient (repeat or comma-separate). Repeat the flag for several values. |
| `--cc <address>` | `string[]` |  | CC recipient. Repeat the flag for several values. |
| `--bcc <address>` | `string[]` |  | BCC recipient. Repeat the flag for several values. |
| `-s, --subject <text>` | `string` |  | Subject. |
| `--text <text|->` | `string` |  | Plain-text body, or - for stdin. |
| `--html <html|->` | `string` |  | HTML body, or - for stdin. |
| `-f, --file <path|->` | `string` |  | Body from a file (.html is HTML), or - for stdin. |
| `--attach <path>` | `string[]` |  | Attach a file (10 MB of files per message in total). Repeat the flag for several values. |
| `--reply-to <address>` | `string[]` |  | Reply-To address. Repeat the flag for several values. |
| `--label <label>` | `string[]` |  | Label for the sent message. Repeat the flag for several values. |
| `--header <Name: value>` | `string[]` |  | Extra header. Repeat the flag for several values. |

#### Examples

```bash
machinemail send --from agent@machinemail.dev --to ada@acme.dev -s "Hello" --text "Hi Ada"
echo "Build passed" | machinemail send --to ops@acme.dev -s "CI" --text -
machinemail send --to ada@acme.dev -s "Report" --file report.html --attach report.pdf
```

## read

Show a message: headers, trust verdict and agent-safe text.

Shows one message with its trust report summary (verdict, score, authentication, flags) and the agent-safe body (trust.content.text: hidden text, zero-width characters, quotes and signatures removed). --original shows the text as the sender wrote it, labelled untrusted; --raw prints the raw RFC 5322 source.

Piped (or with --json) it prints the agent view: headers, the trust verdict, untrusted: true unless the sender authenticated (verified or known), and body: the agent-safe text (body\_source says where it came from; quarantined mail is withheld, as the MCP server does). --full prints the whole API object instead, with the original text and HTML. The inbox can be given as --inbox or as the first argument; without it the CLI finds it.

```bash
machinemail read <message-id> [flags]
```

#### Arguments

| Field | Type | | Description |
| --- | --- | --- | --- |
| `<message-id>` | `string` | required | Message id (msg_...), optionally preceded by its inbox |

#### Flags

| Field | Type | | Description |
| --- | --- | --- | --- |
| `--inbox <inbox-id>` | `string` |  | Inbox the message is in (found automatically when omitted). |
| `--original` | `boolean` |  | Show the original text instead of the agent-safe text (untrusted). |
| `--full` | `boolean` |  | JSON output: the whole API message (original text and HTML included, untrusted) instead of the agent view. |
| `--raw` | `boolean` |  | Print the raw RFC 5322 message. |

#### Examples

```bash
machinemail read msg_01m4g46p1y0000bqmf6m6yv7h5
machinemail read agent@machinemail.dev msg_01m4...
machinemail read msg_01m4... --json | jq -r .body
machinemail read msg_01m4... --json --full | jq .trust
```

## wait

Wait for a message to arrive (and extract a code or magic link with --otp).

Blocks until a new inbound message matching the filters arrives in the inbox, then prints it. Listens on the SSE event stream and polls as a fallback. Only mail received after the command starts counts; --since 5m also accepts mail from the last five minutes (use it when you triggered the email before calling wait).

Piped (or with --json) it prints the agent view of the message: headers, the trust verdict, untrusted: true unless the sender authenticated, and the agent-safe body (withheld for quarantined mail); --full prints the whole API object instead, original text included.

\--otp prints only the verification code (or the magic link when there is no code), read from the agent-safe text. It only uses mail whose sender authenticated (trust level verified or known, or a DMARC pass, or an aligned SPF or DKIM pass); --allow-unauthenticated also accepts unauthenticated mail and mail without a trust report, and suspicious or quarantined mail is never used. Unauthenticated mail does reach the inbox (labelled UNAUTHENTICATED), so this check matters. --from matches an address exactly, or a domain and its subdomains (github.com matches mail.github.com, not github.com.example.net). --json prints \{code, link, message\_id, ...} instead. Exits 8 on --timeout.

```bash
machinemail wait [flags]
```

#### Flags

| Field | Type | | Description |
| --- | --- | --- | --- |
| `--inbox <inbox-id>` | `string` |  | Inbox to watch (default: MACHINEMAIL_INBOX or the profile inbox). Default: `MACHINEMAIL_INBOX or the profile inbox`. |
| `--from <address|domain>` | `string[]` |  | Sender is this address, or on this domain or a subdomain of it; a bare word matches inside the local part (any of several matches). Repeat the flag for several values. |
| `--subject <text>` | `string[]` |  | Subject contains this, case-insensitive (any of several matches). Repeat the flag for several values. |
| `-t, --timeout <duration>` | `string` |  | Give up after this long: 90, 90s, 5m (default 5m; 0 waits forever). Default: `5m`. |
| `--since <duration|iso>` | `string` |  | Also accept mail received this long ago (5m) or since this time. |
| `--otp` | `boolean` |  | Print only the verification code, or the magic link when there is none. |
| `--link` | `boolean` |  | With --otp: print the magic link even when there is a code. |
| `--pattern <regex>` | `string` |  | With --otp: extract with this regex (first group, else the match). |
| `--link-domain <domain>` | `string` |  | With --otp: the magic link must be on this domain or a subdomain of it (default: the sender domain). Default: `the sender domain`. |
| `--allow-unauthenticated` | `boolean` |  | With --otp: also use mail whose sender did not authenticate, or that has no trust report (never suspicious or quarantined mail). |
| `--full` | `boolean` |  | JSON output: the whole API message (original text and HTML included, untrusted) instead of the agent view. |
| `--poll` | `boolean` |  | Poll only; do not use the event stream. |

#### Examples

```bash
machinemail wait --inbox signup-7f3k@machinemail.dev --otp
CODE=$(machinemail wait --inbox "$ADDR" --from github.com --otp --timeout 2m)
machinemail wait --inbox "$ADDR" --subject invoice --json | jq .trust.level
machinemail wait --inbox "$ADDR" --otp --allow-unauthenticated   # e.g. a test sender without SPF/DKIM
```

## tail

Follow live events (messages, deliveries, inboxes) as they happen.

Streams GET /v0/events/stream. On a terminal: one line per event with the trust badge of inbound mail, and a status line. Piped: NDJSON, one \{id, event, data} per line. Reconnects on its own and resumes with Last-Event-ID; pass --last-event-id to resume from an earlier run. Ctrl-C stops it (exit 0).

```bash
machinemail tail [flags]
```

#### Flags

| Field | Type | | Description |
| --- | --- | --- | --- |
| `--inbox <inbox-id>` | `string[]` |  | Only these inboxes. Repeat the flag for several values. |
| `--pod <pod-id>` | `string[]` |  | Only these pods. Repeat the flag for several values. |
| `-e, --event-types` | `string[]` |  | Only these event types (comma-separated or repeated). Repeat the flag for several values. |
| `--last-event-id <evt_...>` | `string` |  | Replay events after this one first. |
| `--max <n>` | `integer` |  | Stop after n events. |

#### Examples

```bash
machinemail tail
machinemail tail --inbox agent@machinemail.dev -e message.received
machinemail tail -e message.bounced,message.complained | jq -c .data.message.to
```
