A handful of top-level commands cover what agents do with email most often, each in one line that works the same from a shell script or a model’s tool call. They all print JSON when piped, and the reading commands only ever hand over the agent-safe text of a message.

| Job | Command |
| --- | --- |
| Get an address | `machinemail inbox new` |
| Wait for a verification code | `machinemail wait --otp` |
| Wait for any message | `machinemail wait --from ... --subject ...` |
| Read a message safely | `machinemail read <message-id>` |
| Send | `machinemail send` |
| Follow mail live | `machinemail tail` |
| Start with no account | `machinemail register`, then `machinemail claim` |

`send`, `wait` and `read` use a default inbox when you leave it out: `MACHINEMAIL_INBOX`, then the inbox saved in the profile by `register`, then the inbox of an inbox-scoped key.

## Sign up for a service

The most common agent job: create an address, use it to sign up somewhere, and read back the verification code.

1. ### Create a throwaway inbox

Terminal

```
INBOX=$(machinemail inbox new --ephemeral --ttl 30m)
echo "$INBOX"   # 5emr9t4q6fwv@machinemail.dev
```

Without `--username` the address gets a random, hard-to-guess local part. `--ephemeral` makes the inbox delete itself, with its mail, after the TTL (one hour unless `--ttl` says otherwise; `--ttl` takes `600`, `10m`, `2h` or `1d`); each inbound message extends it. On a terminal it shows the inbox and the next command to run:

```
✓ Created 5emr9t4q6fwv@machinemail.dev
     ephemeral, expires 2026-10-09 16:15 UTC unless mail arrives
     Wait for a verification code sent to it:
     machinemail wait --inbox 5emr9t4q6fwv@machinemail.dev --otp
```

2. ### Submit the sign-up form

Have the agent (or your test) enter `$INBOX` in the service’s sign-up form.

3. ### Wait for the code

Terminal

```
CODE=$(machinemail wait --inbox "$INBOX" --from acme.dev --otp --timeout 2m)
echo "$CODE"   # 482913
```

`wait` blocks until a new message matching the filters arrives, then prints only the code, or the magic link when the message has no code. It exits 8 if nothing arrives within `--timeout` (default 5 minutes; `0` waits forever).

If the email may have arrived before `wait` started (you submitted the form first), add `--since 5m` so recent mail counts too. Only mail received after `wait` starts counts otherwise.

With `--json` you get the code and where it came from:

```
{
  "code": "482913",
  "value": "482913",
  "message_id": "msg_01m4gnfsw200001e83690x4dm8",
  "inbox_id": "5emr9t4q6fwv@machinemail.dev",
  "from": "noreply@acme.dev",
  "subject": "Your Acme verification code",
  "trust_level": "verified"
}
```

### How the code is found, and what is trusted

`wait --otp` is built so that a hostile email cannot hand the agent a code of its choosing:

- The code is read from `trust.content.text`, the agent-safe text of the [trust report](/content/docs/trust/index.html): hidden text and zero-width characters are removed first, so a code planted in invisible HTML is not picked up.
- Only mail whose sender authenticated is used: trust level `verified` or `known`, or a DMARC pass, or an aligned SPF or DKIM pass. Unauthenticated mail does reach the inbox (labelled `UNAUTHENTICATED`), which is why this check matters. Suspicious and quarantined mail is never used.
- `--from github.com` matches `github.com` and its subdomains, never a lookalike such as `github.com.example.net`. `--from` also takes a full address.
- A magic link must come from `trust.links`, unflagged, and be on the sender’s domain or a subdomain of it. Pass `--link-domain` when the link is on a parent domain; `--link` prints the link even when there is also a code.
- `--pattern <regex>` replaces the code pattern (the first capture group, else the whole match).

For a test sender without SPF or DKIM (a local mail server, an injected test message), add `--allow-unauthenticated`. It still skips suspicious and quarantined mail.

### Wait for a message

Without `--otp`, `wait` prints the message itself. Filter by sender and subject (each can repeat; any match counts):

Terminal

```
machinemail wait --inbox "$INBOX" --from billing@acme.dev --subject invoice --timeout 10m
```

`wait` listens on the event stream and polls as a fallback (every 5 seconds while the stream is up, 2 seconds without it), so it still works when the stream drops. `--poll` uses polling only.

### Read a message safely

Terminal

```
machinemail read msg_01m4gnfsw200001e83690x4dm8
```

On a terminal, `read` shows the headers, the trust verdict and the agent-safe text:

```
Your Acme verification code
FROM      noreply@acme.dev
TO        5emr9t4q6fwv@machinemail.dev
RECEIVED  2026-10-09 15:46 UTC
MESSAGE   msg_01m4gnfsw200001e83690x4dm8
INBOX     5emr9t4q6fwv@machinemail.dev
THREAD    thd_01m4gnfsw20001nhk84cgww2d0
LABELS    INBOX, UNREAD
TRUST     UNKNOWN  score 0.69  otp
AUTH      spf none dkim none dmarc none
SENDER    acme.dev, first contact
  ● Unknown: no authentication passed
  ● First message from this sender (no prior correspondence)
  ● Classifier urgency: Deadline or expiry wording: "expires"
  … 5 more (--json for the full report)

── AGENT-SAFE TEXT ─────────────────────────────────────────────────────────────
Your Acme verification code is 482913. It expires in 10 minutes.
```

This sender did not authenticate (no SPF, DKIM or DMARC pass), so the message is `UNKNOWN` and untrusted; `wait --otp` would only take a code from it with `--allow-unauthenticated`.

### Send

Terminal

```
machinemail send --from "$INBOX" --to ada@example.com -s "Weekly report" --text "Numbers attached." --attach report.pdf
```

- `--to`, `--cc` and `--bcc` repeat or take comma-separated addresses.
- The body comes from `--text`, `--html` or `--file` (a `.html` file is sent as HTML); `-` reads it from stdin.
- `--attach` repeats; attachments are base64-encoded, at most 10 MB of files per message.
- `--header "Name: value"`, `--reply-to` and `--label` repeat too.

Terminal

```
echo "Build passed" | machinemail send --to ops@acme.dev -s "CI" --text -
```

Piped, `send` prints the new `message_id`, `thread_id` and `timestamp`. A send is never retried after a server error, so a script cannot send the same message twice. To reply in a thread, use `machinemail messages reply <inbox-id> <message-id> --text ...`.

### Follow mail live

Terminal

```
machinemail tail --inbox "$INBOX"
```

On a terminal, `tail` prints one line per event with the trust badge of inbound mail, under a status line:

```
15:46:39  message.received          UNKNOWN      5emr9t4q6fwv@machinemail.dev  noreply@acme.dev  Your Acme…
● LIVE  5emr9t4q6fwv@machinemail.dev  1 event  Ctrl-C to stop
```

Piped, it prints NDJSON, one `{id, event, data}` per line, which suits an agent reading a stream or a log file:

Terminal

```
machinemail tail --inbox "$INBOX" -e message.received >> mail.ndjson
```

### Register an agent without a human

Terminal

```
machinemail register --name "Research agent"
```

```
✓ Registered a sandbox agent: agent-ygsa50y7nfzo@machinemail.dev
PROFILE  agent (use --profile agent)
AGENT    reg_01m4gngsfh00008vwn3sjg847x
EXPIRES  2026-10-10 15:46 UTC unless claimed
SANDBOX  this inbox only, daily send caps until claimed

CLAIM  Hand this to a human. They sign in, enter the code, and the agent joins their organization.
  https://machinemail.dev/claim?claim_attempt_token=cat_...
  code 972384  expires in 10 minutes
```

`register` creates a sandbox organization with one inbox on `machinemail.dev`, gets an access token, and saves everything in a profile: the current profile if it has no key, otherwise one named `agent` (or `--profile`). It never replaces a profile that holds an API key unless you pass `--force`. The token lasts an hour; the CLI renews it before each command, so you never handle it. Tokens are never printed.

The sandbox:

- reaches only its own inbox,
- can send 10 messages to 10 different recipients per 24 hours, at most 5 per message (see [Rate limits](/content/docs/reference/rate-limits#sandbox-sends/index.html)),
- expires after 24 hours unless a human claims it.

The new inbox becomes the profile’s default inbox, so `send`, `wait` and `read` work without `--inbox`:

Terminal

```
machinemail wait --profile agent --otp
```

### Hand it to a human

The claim is how the agent’s human takes it over. Give them the link and the 6-digit code; they sign in to MachineMail, enter the code, and the agent and its inbox move into their organization with full access.

Terminal

```
machinemail claim --wait                       # a new code, then wait for the human
machinemail claim --email ada@acme.dev --wait  # only Ada can claim it
```

Codes expire after 10 minutes and a new one replaces the old. `claim` (and `register --wait`) with `--wait` blocks until the human finishes, then saves the post-claim credentials to the profile. A claim code that expires while waiting exits 8. `register --email` limits who can claim from the start; `register --no-claim` skips the first code.

A claimed agent’s token can be renewed for about a day. For an agent that keeps working longer, create an API key for it (scoped to its inbox if that is all it needs) and save it to a profile of its own:

Terminal

```
printf %s "$KEY" | machinemail login --key - --profile research-agent
```
