Skip to the documentation
CLI

Scripting

JSON output, streams, exit codes, environment variables, and running unattended or in CI.

JSON output

With --json, stdout holds only JSON, indented by two spaces, while notes and progress stay on stderr, and nothing prompts. A list prints one { items, hasMore, nextCursor } document, with --all too, on a terminal or in a pipe, and hasMore is true when --max stopped it. The few lists with a shape of their own, such as addresses list, say so in their help. An API object prints as the API returned it, and a hand-written command prints the object its help describes.

Terminal
nextmail whoami --json | jq -r .workspaceIdnextmail emails list --status failed --json | jq -r ".items[].id"

An error goes to stderr as one line of JSON, and the exit code is the one a person would get:

stderr
{"error":{"type":"permission_error","code":"insufficient_scope","message":"This API key does not have the \"domains:write\" scope.","hint":null,"next":"Use an API key that has domains:write.","status":403,"requestId":"req_7Hc2kQ","param":null,"docUrl":"https://nextmail.sh/docs/api/errors#insufficient_scope","exitCode":4}}
FieldWhat it holds
typeThe API error type, or cli_error, network_error or internal_error for a failure inside the CLI
codeA stable code such as insufficient_scope, not_signed_in or unknown_flag
messageWhat went wrong, in one sentence
hint, nextWhat to try, and the command to run next, or null
status, requestId, param, docUrlFrom the API when the error came from it, otherwise null
exitCodeThe exit code the process ends with

Streams

Some output is a stream of JSON objects, one per line, so a pipeline can handle each item as it arrives:

  • A resource list with --all piped without --json, or with --ndjson. --max <n> stops after that many items.
  • nextmail temp watch --json, one line per new message.
  • nextmail mcp serve, one JSON-RPC message per line in each direction.
Terminal
nextmail contacts list --all > contacts.ndjsonnextmail emails list --status failed --all --max 500 | jq -r .id

Exit codes

CodeMeaning
0Done, or a dry run that stopped before its first change
1An unexpected failure, a server error, or a send that failed
2A usage error: a bad argument, an unknown command or flag, a value or confirmation that could not be asked for, or an origin or path the CLI will not send a credential to
3Not signed in, or the sign-in was refused, has expired or was signed out while the command ran
4Not allowed: a missing scope or permission, a verification code that could not be asked for or is paused, or an API key where a browser sign-in is needed
5Not found
6A conflict with the current state
7The input was invalid
8Rate limited, or the AI allowance is spent
9The network failed or timed out
10Cancelled: you declined a confirmation or a prompt
130, 143Stopped by Ctrl+C, or by SIGTERM

Environment variables

VariableWhat it does
NEXTMAIL_API_KEYAn API key to use instead of the active profile. --profile still picks a saved one
NEXTMAIL_PROFILEThe saved profile to use
NEXTMAIL_BASE_URLThe API origin for NEXTMAIL_API_KEY, --api-key and commands that send no credential. A saved sign-in only ever goes to the API it signed in to
NEXTMAIL_APP_URLThe web app origin a browser sign-in saves, and the one open and docs links use whatever profile is active, so a saved sign-in never refuses it. Unset, links use the web app the profile signed in through, a local web app when the API is local, or https://nextmail.sh
NEXTMAIL_CONFIG_DIRWhere profiles and inbox tokens are kept, ~/.nextmail unless set
NEXTMAIL_NO_UPDATE_CHECKNever check npm for a newer release. NEXTMAIL_DISABLE_UPDATE_NOTICE does the same
NO_COLOR, FORCE_COLOR=0No colour
CINever prompt, never open a browser, never check for updates. Most CI services are recognised without it
VISUAL, EDITORThe editor send and reply open for a body

Unattended runs

The CLI prompts only when stdin and stdout are both terminals, and none of --json, --no-input or CI applies. Otherwise:

  • A missing required value stops with exit code 2 and names the flag to pass.
  • A destructive command stops with Refusing to run unattended. Pass --yes to confirm. and exit code 2, unless you pass --yes.
  • A change that needs a verification code stops with exit code 4, because nobody can type it. Use an API key, or run nextmail verify first.
  • A browser sign-in stops with exit code 2 before anything is registered, so sign in with nextmail login --with-token or NEXTMAIL_API_KEY instead.

An AI agent such as Claude Code or Codex can start from nextmail agents. The For AI agents page covers the same ground: help as data, dry runs, missing scopes and verification codes.

In CI

Give the job an API key with only the scopes it needs, keep it in a secret, and let NEXTMAIL_API_KEY carry it. Nothing is saved, nothing prompts, and no update check runs.

.github/workflows/deploy.yml
- name: Tell the team  env:    NEXTMAIL_API_KEY: ${{ secrets.NEXTMAIL_API_KEY }}  run: |    npx -y @nextmail/cli@0.0.1 send \      --from deploys@acme.com \      --to team@acme.com \      --subject "Deployed ${{ github.sha }}" \      --text "Build ${{ github.run_number }} is live." \      --idempotency-key "deploy-${{ github.run_id }}"
Wait for a sign-up email
ADDRESS=$(npx -y @nextmail/cli@0.0.1 temp new --ttl 15)./signup-test.sh "$ADDRESS"npx -y @nextmail/cli@0.0.1 temp watch --first --json | jq -r .snippetnpx -y @nextmail/cli@0.0.1 temp delete --yes
Fail on failed sends
failed=$(nextmail emails list --status failed --json | jq ".items | length")test "$failed" -eq 0

Pass --idempotency-key on a send that a pipeline may retry, and derive it from what made the send necessary, such as a run id. Running the step again then returns the first send instead of mailing twice.