## machinemail doctor

Start with `doctor`, then look up the error below. Every error goes to stderr with a hint, and the [exit code](/content/docs/cli/output-and-scripting#exit-codes/index.html) says what kind of failure it was.

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

Terminal

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

Terminal

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

## 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 `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.

### 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.
