CLI outputs and troubleshooting
The CLI prints one JSON document to standard output. Complete setup before using a real mailbox.
JSON results
A successful mutation can return:
{"status":"Moved","emailId":"immutable-message-id","targetId":"folder-id"}
A successful read can return:
{"status":"Listed","labels":[{"labelId":"category-id","labelColor":"preset4","labelName":"Important"}]}
A failure can return:
{"status":"Errored","operation":"email.move","errorReason":"The message was not found.","graphCode":"ErrorItemNotFound","graphStatusCode":404}
The error can include requestId when Microsoft supplies it. Include it in a provider incident report without sharing credentials.
Exit codes
| Code | Meaning | Examples |
|---|---|---|
0 | Successful or unchanged idempotent result | Listed, Sent, Moved, Added, Removed, Unchanged |
1 | Any failure that is not CliValidationError | Provider/runtime failures and some input/parser errors |
2 | A CliValidationError | Validated missing options, invalid priority, or missing configuration on paths that use this error type |
user lookup and email get, attachments, reply, reply-all, and forward use a newer command path. Missing/invalid arguments there can throw plain errors or argument-parser errors and return 1. Thus, not every input failure returns 2, and not every 1 means a provider failure.
Automation must require exit 0 before parsing success fields. For a nonzero exit, inspect the Errored result. Do not choose retries solely from code 1. A send error can still require inspection of Sent Items.
Check exit status before parsing a success field:
bun run cli -- label list --user-id person@example.com
result=$?
if [ "$result" -ne 0 ]; then
echo "Graph CLI failed with exit code $result"
fi
Status values
| Status | Meaning |
|---|---|
Listed | List retrieval finished |
Found | Resource retrieval finished |
Created | Folder/category created |
Updated | Folder/category updated |
Sent | Sent Items copy and internet message ID observed |
Arrived | Matching recipient copy found |
Seen | Message marked read |
Moved | Message/folder moved |
Added, Removed | Category membership changed |
Unchanged | Requested idempotent category state already holds |
Deleted | Message moved to Deleted Items, or folder/category deleted |
PermanentlyDeleted | Provider permanent-delete operation completed |
Errored | Operation failed |
Sent is a sender-side observation. It is not proof of recipient delivery. Arrived is a separate result.
Destructive commands
email permanent-delete, folder delete, and label delete require --confirm. It authorizes real modification/deletion. email delete does not ask for it because that command moves the message to Deleted Items. Permanent deletion requires an eligible message in Deleted Items.
Diagnose failures
Missing environment variable
The JSON error names the required variable, such as CLIENT_SECRET. Correct the CLI's secure configuration for TENANT_ID, CLIENT_ID, and CLIENT_SECRET. Do not substitute another service's variable names.
401 Unauthorized
Check an expired/invalid secret, tenant/client identity, and whether the credential belongs to that tenant.
403 Forbidden
Check application permissions, administrator consent, Exchange application scope, protected system-folder restrictions, and retention/compliance rules. See Graph setup.
404 or ErrorItemNotFound
Check mailbox ownership of the ID, immutable-ID format, cross-mailbox copy confusion, and permanent deletion. For a recipient copy, run email wait with the sender's internetMessageId, then use the returned emailId.
Folder name missing or ambiguous
Names match exactly without case sensitivity. Partial names do not match. List folders:
bun run cli -- folder list --user-id person@example.com
Use the exact name or ID. Different hierarchy branches can share a display name. An ambiguity error returns matching IDs. Choose the intended ID explicitly.
Category name missing
List categories, then use --label-id or the exact displayed name:
bun run cli -- label list --user-id person@example.com
Send confirmation timeout
Graph accepted submission. But the tool could not observe the Sent Items copy and its internetMessageId within 30 seconds. The message can already be sent. Inspect Sent before retrying to prevent duplicates.
Arrival timeout
Delivery can exceed --timeout-ms. Check whether recipient address and --user-id select the same mailbox. A rule or quarantine can redirect the message. A folder filter can also hide it. Increase the timeout or remove the folder filter after checking the original send.
429 Too Many Requests
Reduce request frequency, increase --poll-interval-ms, and avoid repeated full-mailbox lists. Honor provider/SDK retry guidance.
Automation
Store the full successful JSON before extracting a field. This keeps failure handling separate from jq:
if result=$(bun run cli -- email send \
--user-id person@example.com \
--to recipient@example.com \
--subject "Automated test" --body "Body" --priority normal); then
email_id=$(printf '%s' "$result" | jq -r '.emailId')
fi
This example sends real mail. Use an authorized test target.
| Use a command when | Use a workflow when |
|---|---|
| One operation is enough | Several dependent steps are necessary |
| You inspect state manually | You need a repeatable scenario |
| A shell owns intermediate state | TypeScript types connect the steps |
| IDs are known | One result supplies the next input |