Authentication
Sign the CLI in through the browser with the device flow, give it an API key in CI, keep several keys in profiles, log out and revoke, and see where the key is stored and who can read it.
The CLI authenticates every request with an API key (mm_live_...), sent as Authorization: Bearer. There are three ways to get one into it:
| Situation | Use |
|---|---|
| You, at a terminal, with a browser | machinemail login: approve a code in the dashboard, get a new key. |
| CI, scripts, containers | An existing key in MACHINEMAIL_API_KEY, or saved with login --key -. |
| An agent with no human and no account | machinemail register: a sandbox inbox and its own credentials. See Agent workflows. |
Sign in with the browser
machinemail login uses the OAuth 2.0 device authorization grant (RFC 8628), the same flow as gh auth login: the CLI never sees your password or session, and you approve on a device where you are already signed in.
Start the sign-in
The CLI asks the API for a code and shows it:
Approve the code
Press Enter to open the link, or open it on any device: your laptop when the CLI runs over SSH, your phone, another browser. If you are not signed in to the dashboard, it asks you to sign in first (see the Quickstart if you have no account yet).
The page shows the code, the access the key will get and when it was requested. Check that the code matches the one in your terminal, then choose Approve. Deny stops the CLI and creates nothing. Approve only a code you asked for yourself.
The key is saved
The API creates a new organization key named
CLI on <hostname>and hands it to the waiting CLI, once. The CLI saves it to the current profile:
The key has every permission except inbound_raw, and it is listed on the dashboard’s API keys page, where you can revoke it. Codes expire after 10 minutes and work once.
| Outcome | What the CLI does | Exit code |
|---|---|---|
| Approved | Saves the key and prints who you are signed in as. | 0 |
| Denied in the browser | Sign-in was denied in the browser. Nothing is saved. | 3 |
| Code expired (10 minutes) | The sign-in code expired before it was approved. | 8 |
| Ctrl-C | Interrupted; nothing was saved. | 130 |
Without a browser
When the CLI finds no browser to open (over SSH, in a container), it shows the code box without the Enter prompt: open the URL on any device. When stdin or stderr is not a terminal, or CI is set, it prints the link and the code as plain lines on stderr and waits:
machinemail login --no-browser never opens a browser, even when it could.
Use an existing API key
Nobody is there to approve a code in CI. Create a key on the dashboard’s API keys page (or with machinemail api-keys create), store it as a secret, and give it to the CLI in one of these ways. Each keeps the key out of your shell history and out of ps:
login --key, --with-key (the same as --key -) and --paste check the key against GET /v0/auth/me before saving it, and print the same summary as whoami. A key the API rejects is not saved (exit 3). --no-verify saves without calling the API, for setting up a machine offline.
--api-key <key> also takes a literal key, but the CLI warns that it shows in ps and your shell history. Prefer the environment variable or stdin.
Note
An organization key can do everything in your organization. For an agent or a CI job that works with one inbox, create a key scoped to that inbox, or to a pod. See API keys.
Which key is used
The first one found wins:
--api-keyMACHINEMAIL_API_KEY- the
api_keyof the profile in use
MACHINEMAIL_API_KEY therefore shadows a key saved by login. machinemail doctor warns when that happens, and whoami says where the key came from:
Profiles
A profile is a named API key and base URL. Use one per organization, environment or agent:
* marks the current profile. The profile in use is --profile, then MACHINEMAIL_PROFILE, then the config file’s current_profile, then default. See Configuration for what else a profile holds.
machinemail profiles delete <name> removes a profile and its stored key (on a terminal you type the name to confirm; elsewhere pass --yes). It does not revoke the key.
Log out
logout deletes the stored key from the profile; the profile and its base URL stay. On a terminal it first asks whether to revoke the key on the server too (DELETE /v0/api-keys/{id}), so it stops working everywhere:
| Command | Removes the local copy | Revokes the key on the server |
|---|---|---|
machinemail logout | yes | asks on a terminal; no otherwise |
machinemail logout --yes | yes | yes |
machinemail logout --revoke | yes | yes |
machinemail logout --no-revoke | yes | no |
If revoking fails, nothing is removed, so you can retry or revoke the key on the dashboard. logout only touches the config file: when MACHINEMAIL_API_KEY is set it says so, and you unset the variable yourself. For a profile created by register, logout also removes the agent credentials that renew its token.
Piped, logout prints what it did:
Where the key is stored
Keys saved by login, register and config set api_key live in the config file, by default ~/.config/machinemail/config.json ($XDG_CONFIG_HOME/machinemail/config.json when that is set; MACHINEMAIL_CLI_CONFIG points at another file).
- The file is written with mode
0600(read and write for you only) inside a0700directory, through a temporary file and a rename, so it is never half-written. machinemail doctorwarns when the file is readable by other users.- The key is stored in plain text, as with most CLIs: anyone who can read the file as you can use it. Do not commit it or copy it into images; in CI, use the environment variable.
- The CLI only ever prints keys masked (
mm_live_…hdYv), including inconfig list,whoami,--verboserequest logs and error messages. Agent tokens show as(set).
See Configuration for the file’s format.