Overview
The CLI is a client, not a new surface: it wraps the same three GETs as the API and the same tools as the MCP server, counts against the same read limits (60/min per inbox, 600/min per IP), and sees the same 24-hour retention. Nothing is stored between commands beyond a small update note (see Update).
Platforms: macOS and Linux on arm64 and x64, plus Windows x64 under Git Bash or WSL. An inbox argument accepts a bare local part (qa) or a full address (qa@1mail.dev); letter case never matters. Every inbox is public — the banner above is the contract.
Install
Installs to ~/.local/bin (override with ONEMAIL_INSTALL_DIR); the script verifies the sha256 published beside each binary before installing anything, and tells you what to add to PATH if needed.
curl -fsSL https://1mail.dev/install.sh | bashDirect downloads, if you'd rather not pipe a script: the manifest lists every binary with its checksum — 1mail-darwin-arm64, 1mail-darwin-amd64, 1mail-linux-amd64, 1mail-linux-arm64, 1mail-windows-amd64.exe — under 1mail.dev/cli/latest/, with pinned versions under 1mail.dev/cli/v1.0.0/.
Package managers
npm i -g 1mailpnpm add -g 1mailyarn global add 1mailbun add -g 1mailOne-off, no install — npx
npx 1mail <command>Runs any command once with the latest release; nothing stays installed. Handy in scripts and CI: npx 1mail list <inbox> --json.
Every channel installs the same launcher and platform binary. Package-manager installs update through their manager — e.g. npm i -g 1mail@latest — and 1mail upgrade defers to them by design; only the curl install updates itself.
Update
1mail upgrade fetches the published manifest, downloads the build for your platform, verifies its sha256, and atomically replaces the running executable — any failure leaves your current binary untouched. Re-running the installer does the same thing.
$ 1mail upgrade
1mail 1.0.0 → 1.0.2
downloading 1mail-darwin-arm64…
verified 6.1 MiB — installed
upgraded: 1mail 1.0.21mail also checks for updates at most once per day and, when one exists, prints a single line to stderr — stdout and --json pipelines are never touched. Set NO_UPDATE_NOTIFIER=1 to silence it entirely.
Commands
1mail newLink to this command
Generate fresh, unguessable inbox addresses — locally, with no network call. One address per line, ready to hand to whatever sends mail.
1mail new # k7m2xq9vbt4w@1mail.dev
1mail new --count 3 # three of themTwelve characters from an unambiguous alphabet (no i, l, o) ≈ 60 bits of randomness — anyone who knows the address can read its inbox, so it must not be guessable. The result is valid by construction (there is no creation step on the service) and never a reserved or operator local part.
| Flag | Default | Description |
|---|---|---|
--count N | 1 | How many addresses to print (1–100). |
--json | off | n/a — new is offline; output is one address per line. |
1mail listLink to this command
List an inbox, newest first: received, from, subject, id. An inbox with no messages yet is not an error — it materializes on its first mail.
1mail list qa
1mail list qa --limit 50
1mail list qa --since-id 0192a0c0-0000-7000-8000-000000000001 --json | jq '.messages[0]'
1mail list qa --since-id 0192a0c0-0000-7000-8000-000000000001 --wait 20000| Flag | Default | Description |
|---|---|---|
--limit N | 10 | Page size, 1–1000. (The API's own default is 100; the CLI narrows it for the table view.) |
--since-id ID | — | Only messages newer than this id — your poll cursor. |
--older-than-id ID | — | Page back from this id. |
--wait MS | off | Hold up to MS ms (0..30000) for mail newer than --since-id — one held request instead of a poll loop. An empty answer means the window elapsed; run again to keep waiting. Not with --older-than-id. |
--json | off | Print the API payload verbatim instead of the table. |
--api URL | — | Override the API base (default https://api.1mail.dev/v1, or env ONEMAIL_API_BASE). |
1mail readLink to this command
Print one parsed message: From/To/Subject, received and expiry times, the message id, attachment metadata, and the text part.
1mail read qa 0192a0c0-0000-7000-8000-000000000001
1mail read qa 0192a0c0-… --json | jq -r '.html' # the html partThe id is the one list prints. The CLI never renders HTML: --json prints the API payload verbatim — including the unsanitized html field, which is untrusted by contract; treat it accordingly. If a message has no text part, read says so and points at --json or raw.
| Flag | Default | Description |
|---|---|---|
--json | off | Print the API payload verbatim (headers, html, attachments). |
--api URL | — | Override the API base (default https://api.1mail.dev/v1, or env ONEMAIL_API_BASE). |
1mail rawLink to this command
Print the untouched original message bytes (message/rfc822) to stdout.
1mail raw qa 0192a0c0-… > message.emlExactly what the sender's server delivered — headers, MIME structure, attachments. Assert on this in tests when the parsed view is not enough.
1mail upgradeLink to this command
Update the 1mail binary in place. See Update, above, for the exact protocol.
1mail upgrade # "1mail is up to date (1.0.0)" when it is1mail help · versionLink to this command
Usage, per-command help, and the installed version.
1mail # the same as: 1mail help
1mail help list # per-command help; flags work too: 1mail list --help
1mail version # 1mail 1.0.0 — also: 1mail -v / 1mail --versionScripting
The reliable wait-for-mail loop — generate an address, point the app at it, and let each call hold server-side for up to 20 s (--wait — one read per call, however long it holds):
addr=$(1mail new)
# …trigger the email to "$addr", then:
until 1mail list "$addr" --wait 20000 --json | jq -e '.messages | length > 0' >/dev/null; do
: # each call held 20 s server-side — retry immediately
done
1mail list "$addr" --json | jq -r '.messages[0].id'--since-id returns only newer messages, so steady-state waits stay cheap; messages expire after 24 hours, so long-running scripts should check expires_at rather than wait forever. Reads are rate limited — the same published limits as every other client.