Skip to content
machinemail

Troubleshooting

Diagnose CLI problems with machinemail doctor and --verbose, and fix the common errors - no API key, a rejected key, an unreachable API, a broken config file, wait timeouts, deletes that need --yes and sign-in codes that expire.

Start with doctor, then look up the error below. Every error goes to stderr with a hint, and the exit code says what kind of failure it was.

machinemail doctor

doctor runs every check and reports each one, without stopping at the first failure:

Terminal
machinemail doctor
text
MACHINEMAIL doctor

OK    CONFIG FILE   /home/ada/.config/machinemail/config.json (0600)
OK    PROFILE       default (profile)
OK    API           https://api.machinemail.dev (profile)
OK    API KEY       mm_live_…hdYv (profile)
OK    CONNECTIVITY  GET /health 200 in 3ms
OK    API READY     ready
OK    KEY ACCEPTED  organization key, organization org_01m4gnem700000j9kk6vas9c0c
OK    CLOCK         in sync with the API
OK    CLI           0.1.0 (bun 1.4.2, linux-x64); update check off

No problems found  9 ok

A failing check says how to fix it:

text
WARN  CONFIG FILE   /home/ada/.config/machinemail/config.json (not created yet)
                    Run `machinemail login` to create it.
OK    PROFILE       default (default)
OK    API           https://api.machinemail.dev (default)
FAIL  API KEY       none
                    Run `machinemail login`, or set MACHINEMAIL_API_KEY.
CheckWhat it looks at
Config fileThe file exists, parses, and is not readable by other users.
Profile, APIWhich profile and base URL are in use, and where each came from. Warns about plain http:// to a remote host.
API keyA key is set, and whether MACHINEMAIL_API_KEY shadows the profile’s.
ConnectivityGET /health answers.
API readyGET /health/ready answers.
Key acceptedGET /v0/auth/me accepts the key, and what it is scoped to.
ClockSkew against the API’s Date header. Over 30 seconds warns, over 5 minutes fails.
CLIVersion, runtime and target, and the update check.

doctor exits with the code of the first failure (7 config, 6 network, 3 auth, 1 other) or 0; warnings do not fail it. --offline skips the checks that call the API, --profile checks another profile, and --json gives { ok, checks: [{ id, label, status, detail, hint? }] }:

Terminal
machinemail doctor --json | jq '.checks[] | select(.status != "ok")'

See the requests

-v/--verbose logs each request and response to stderr: method, URL, status, timing and bodies. API keys, webhook secrets, claim tokens and agent tokens are redacted, so you can paste the log into an issue.

Terminal
machinemail inboxes list --verbose 2> requests.log

When you report a problem, include machinemail version --json and the doctor --json output.

Common errors

No API key (exit 3)
text
{"error":{"type":"cli","message":"No API key"},"hints":["Run `machinemail auth login`, or set MACHINEMAIL_API_KEY."]}

The profile in use has no key and none was given. Run machinemail login, set MACHINEMAIL_API_KEY, or pass --profile if the key is saved under another profile name (machinemail profiles list).

Missing or invalid API key (exit 3)
text
{"error":{"type":"unauthorized","message":"Missing or invalid API key"},"status":401}

The API rejected the key: it was revoked, mistyped, or belongs to another API. machinemail whoami shows which key is in use and where it came from; a stale MACHINEMAIL_API_KEY in your shell overrides the profile’s key. A 403 means the key is valid but lacks the permission or scope for that call, for example an inbox-scoped key used on another inbox.

Could not reach the API (exit 6)
text
{"error":{"type":"cli","message":"Could not reach http://127.0.0.1:8080: connection refused"},"hints":["Check the base URL (--base-url, MACHINEMAIL_BASE_URL or the profile) and your connection."]}

Check the base URL with machinemail config get base_url --json (it says where the value came from), and that the host resolves from where the CLI runs. Behind a proxy, set HTTPS_PROXY. Failed GET, PUT and DELETE requests are already retried three times before this error.

Could not read the config file (exit 7)
text
{"error":{"type":"cli","message":"Could not read /home/ada/.config/machinemail/config.json: JSON Parse error: Expected '}'"},"hints":["Fix the JSON or delete the file and run `machinemail auth login` again."]}

The config file is not valid JSON, usually after a hand edit. Fix it, or move it aside and sign in again. Use config set and profiles to change it rather than an editor.

Unknown command or flag (exit 2)
text
{"error":{"type":"usage","message":"Unknown flag --usernam"},"hints":["Did you mean --username?"]}

Nothing was sent. The hint suggests the closest command, action, flag or enum value. Command flags go after <resource> <action>; machinemail <resource> <action> --help lists them.

Missing --from or --inbox (exit 2)
text
{"error":{"type":"usage","message":"Missing --from"},"hints":["Pass the inbox address, set MACHINEMAIL_INBOX, or use a profile from `machinemail register`."]}

send, wait and read need an inbox. Pass it, or set a default with MACHINEMAIL_INBOX. An inbox-scoped key or a profile from register provides one automatically.

A delete says add --yes (exit 2)
text
{"error":{"type":"usage","message":"Delete inbox support-agent@machinemail.dev: add --yes to confirm"},"hints":["There is no terminal to ask on, so nothing was sent."]}

Deletes ask for confirmation on a terminal. In a script, a pipe, or with CI set, there is nobody to ask, so pass --yes.

wait timed out (exit 8)
text
{"error":{"type":"cli","message":"No matching message arrived in 0:05"},"hints":["Raise --timeout, or use --since if the email was sent before the wait started."]}
  • The email arrived before wait started: add --since 5m.
  • wait --otp skipped the message because its sender did not authenticate: check with machinemail messages list <inbox> and machinemail read <message-id>. If the sender is a test system without SPF or DKIM, add --allow-unauthenticated.
  • --from or --subject did not match: --from is an exact address or a domain and its subdomains; --subject is a case-insensitive substring.
The sign-in code expired (exit 8) or was denied (exit 3)

Sign-in codes are valid for 10 minutes and work once. Run machinemail login again for a new code. If you cannot reach a browser that is signed in to MachineMail, create a key on the dashboard and use machinemail login --paste or --key - instead.

Rate limited (exit 5)

The API answered 429 after the CLI’s own retries, which honor Retry-After. Slow down, or raise MACHINEMAIL_MAX_RETRIES for a batch job. A sandbox agent from register has daily caps on messages and different recipients until it is claimed; the error message states them. See Rate limits.

Warning about --api-key
text
machinemail: warning: a key passed as --api-key <key> is visible to other local processes and lands in shell history; prefer MACHINEMAIL_API_KEY, a profile (machinemail login), or --api-key - to read it from stdin

The command still runs. Pass the key through the environment, a profile, or --api-key - on stdin to silence it.

Garbled output or no color

Set NO_COLOR=1 or pass --no-color for terminals that do not render color, and --color to force it in one that is not detected. Tables use the width the terminal reports; where it reports none (some CI logs), set COLUMNS. For scripts, use --json and never parse tables.