troubleshooting.md
Troubleshooting
Diagnose MachineMail CLI problems with doctor and --verbose, and fix common errors: missing or rejected keys, an unreachable API, broken config and timeouts.
Source: https://machinemail.dev/docs/cli/troubleshooting
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:
machinemail doctor
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:
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.
| Check | What it looks at |
|---|---|
| Config file | The file exists, parses, and is not readable by other users. |
| Profile, API | Which profile and base URL are in use, and where each came from. Warns about plain http:// to a remote host. |
| API key | A key is set, and whether MACHINEMAIL_API_KEY shadows the profile's. |
| Connectivity | GET /health answers. |
| API ready | GET /health/ready answers. |
| Key accepted | GET /v0/auth/me accepts the key, and what it is scoped to. |
| Clock | Skew against the API's Date header. Over 30 seconds warns, over 5 minutes fails. |
| CLI | Version, 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? }] }:
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.
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)
{"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)
{"error":{"type":"unauthorized","message":"Missing or invalid API key"},"status":401}
The API rejected the key: it was revoked or mistyped. 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)
{"error":{"type":"cli","message":"Could not reach https://api.machinemail.dev: host not found"},"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)
{"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)
{"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)
{"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)
{"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)
{"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
waitstarted: add--since 5m. wait --otpskipped the message because its sender did not authenticate: check withmachinemail messages list <inbox>andmachinemail read <message-id>. If the sender is a test system without SPF or DKIM, add--allow-unauthenticated.--fromor--subjectdid not match:--fromis an exact address or a domain and its subdomains;--subjectis 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
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.