---
title: "Troubleshooting"
description: "Diagnose MachineMail CLI problems with doctor and --verbose, and fix common errors: missing or rejected keys, an unreachable API, broken config and timeouts."
canonical: "https://machinemail.dev/docs/cli/troubleshooting"
---

# 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](https://machinemail.dev/docs/cli/output-and-scripting#exit-codes) says what kind of failure it was.

## machinemail doctor

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

```bash title="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.
```

| 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? }] }`:

```bash title="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.

```bash title="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 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)**

```text
{"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)**

```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](https://machinemail.dev/docs/reference/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.
