Read, search, send, and manage email across Gmail, QQ, 163, Outlook and any IMAP/SMTP account from the command line. Use when the user asks to "read my email...
---
name: mailbox
description: Read, search, send, and manage email across Gmail, QQ, 163, Outlook and any IMAP/SMTP account from the command line. Use when the user asks to "read my email", "查邮件", "look up an Amazon order email", "find the customer review notification", "send an email", "回复邮件", "delete spam", "查未读", "show unread", "synchronize my mailbox", "set up MCP for email", or anything that involves listing / searching / reading / writing / classifying messages from one or more mailboxes.
metadata:
author: leeguooooo
version: "0.2.2"
homepage: https://github.com/leeguooooo/Mailbox
keywords:
- mailbox
- email
- imap
- smtp
- gmail
- qq
- 163
- outlook
- 邮件
- 邮箱
---
# Mailbox CLI Skill
Drives the `@leeguoo/mailbox-cli` Node CLI to read and manage email across
multiple IMAP accounts. Returns a stable JSON contract — every response
includes `success: boolean` and, on failure, `error: string` +
`error_code: string` (machine-readable).
## Compatibility — check the CLI version first
**If `mailbox` is not on PATH** (`command not found` / `mailbox: not found`), install it
non-interactively from GitHub Releases (no npm, no auth) before doing anything else:
```bash
curl -fsSL https://raw.githubusercontent.com/leeguooooo/Mailbox/main/install.sh | sh
# installs the prebuilt binary to ~/.local/bin — make sure that's on PATH, then re-probe
export PATH="$HOME/.local/bin:$PATH"; mailbox --version
```
These commands/flags require **mailbox ≥ 2.11.0**:
`--format compact|jsonl`, `email recent`, `cleanup`, `--since`, `--account-unread`,
`--text-only`, the 3-part gid (`account_id:folder:uid`), and `search --timeout`.
Probe before relying on them: `mailbox --version`. Update by re-running the installer above
(`MAILBOX_VERSION=v2.11.2 …` to pin). On an older CLI, use these fallbacks (all available
since early versions):
| Newer | Fallback on < 2.11 |
|---|---|
| `--format compact` | `--lean` (drops noise; doesn't pin the exact field set) |
| `email recent` | `email list` with no `--account-id` (already spans all accounts) |
| `--since 7d` | `--date-from 7d` |
| `--text-only` | `--no-html` |
| `cleanup` | classify in-agent from `email list` output |
To tell whether a command exists, probe `mailbox <cmd> --help --json` and check
`success` — an unknown command returns `success:false` / `error_code:"invalid_argument"`.
## Setup (one time, by the user)
```bash
# 1. Install the CLI from GitHub Releases (no npm/Node needed; prebuilt binary):
curl -fsSL https://raw.githubusercontent.com/leeguooooo/Mailbox/main/install.sh | sh
# (npm is deprecated: `npm install -g @leeguoo/mailbox-cli` may lag the releases)
# 2. Configure accounts (edit credentials):
mkdir -p ~/.config/mailbox
cp $(npm prefix -g)/lib/node_modules/@leeguoo/mailbox-cli/examples/accounts.example.json \
~/.config/mailbox/auth.json
$EDITOR ~/.config/mailbox/auth.json
# 3. (Recommended) install the persistent daemon for ~5-30× faster calls:
mailbox daemon install
mailbox daemon status --json # confirm it's running
# 4. (Optional) wire into Claude Desktop / Code via MCP:
mailbox mcp config --json # prints a paste-ready mcpServers entry
```
If the user hasn't done step 1, every CLI call will fail with `command not found`.
Always probe with `mailbox --version` first when in doubt.
## How to drive this CLI from an agent loop
Always pass `--json` so the response is machine-parseable. Check `success`
before continuing.
### Read / search
```bash
# List recent emails (cache when warm; pass --live to force IMAP).
# 'list' is INBOX-only — passing --folder all warns you to use 'search' instead.
mailbox email list --account-id <id> --limit 20 --json
mailbox email list --account-id <id> --limit 20 --with-preview 200 --json # +body snippet, one trip
mailbox email list --since 7d --json # --since is an alias of --date-from (7d/today/YYYY-MM-DD)
mailbox email list --account-unread --json # also compute account_unread_total (unread across all folders)
# Recent across ALL accounts, merged newest-first (omit --account-id == all accounts):
mailbox email recent --limit 30 --json
mailbox email recent --since 3d --json
# Search (server-side IMAP for Gmail; client-side fallback for QQ/163/Outlook):
mailbox email search --from amazon --subject review --folder all --json
mailbox email search --query "interview" --since 2w --json # relative dates: 2d/3w/1mo/today/yesterday
# ⚠️ search over QQ/163 (broken IMAP SEARCH) does a client-side scan of the folder and can be
# slow. --timeout (default 60s) is a HARD wall-clock bound — even a single stuck QQ/163 scan
# returns by then with partial results + timed_out:true (+ pending_accounts). Still prefer to
# scope it (--account-id and/or --folder INBOX) and use a short --timeout for snappy results.
mailbox email search --query inv --account-id <id> --folder INBOX --limit 20 --json # fast, scoped
# NOTE: on QQ/163/126/sina/aliyun/outlook, IMAP TEXT search is broken,
# so the CLI falls back to envelope-only client-side filtering. That
# means `--query` only matches against `subject + from` for those
# providers — pure body-text matches will be missed. Use `--from` /
# `--subject` for predictable results, or use a Gmail account where
# X-GM-RAW does search the body server-side.
# Read one or many emails (AI-friendly defaults: text only, capped 2000 chars, URLs stripped,
# HTML excluded; HTML-only mail is auto-converted to a text body — see body_source).
mailbox email show <gid> --json # gid = "<account_id>:<folder>:<uid>"
mailbox email show <gid1> <gid2> <gid3> --json # batch — one IMAP connection, spans folders
mailbox email show <gid> --full --json # raw HTML + uncapped + URLs (rarely needed)
mailbox email show <gid> --text-only --json # force no HTML (alias of --no-html)
mailbox email show <gid> --html-max-len 0 --json # 0 = strip HTML, -1 = unlimited, >0 = cap
# Folders:
mailbox email folders --account-id <id> --json
```
The **gid is self-describing** (`account_id:folder:uid`), so `email show <gid>` opens the
right mailbox with no `--folder` — even for results from `search --folder all`. The legacy
2-part `account_id:uid` form still works (folder falls back to the cache, then INBOX).
### Mutate (all dry-run by default)
```bash
mailbox email mark <gid> --read --confirm --json
mailbox email delete <gid> --confirm --json # default moves to Trash; pass --permanent to expunge
mailbox email flag <gid> --set --confirm --json
mailbox email move <gid1> <gid2> --target-folder Archive --confirm --json
mailbox email send --to a@b.com --subject hi --body "..." --confirm --json
# Filtered batch mark/delete by sender/subject (no need to list+collect ids first):
mailbox email delete --from newsletter@shop.com --confirm --json
mailbox email mark --subject "[ci]" --read --confirm --json
mailbox email delete --from spam@x.com --all-folders --confirm --json # span folders; grouped per folder
```
`gid`-based and filtered mutations are **folder-aware**: a 3-part gid mutates in *its* folder
(not INBOX), and `--from/--subject` matches carry their folder. The dry-run preview includes a
`groups` breakdown (per `account_id` + `folder`, with sample subjects) so you can eyeball what
will change before `--confirm`. Filters matching >100 emails require `--confirm`.
**Safety:** `--all-folders` skips special-use folders (Sent / Drafts / Junk / Trash) by
default — pass `--include-special` to include them. Without `--confirm`, every destructive
command returns a JSON dry-run preview and changes nothing.
### Triage / cleanup
```bash
# Classify INBOX and propose a deletion plan (read-only; never deletes):
mailbox cleanup --account-id <id> --json
# Then actually delete the marketing + routine_notification candidates:
mailbox cleanup --account-id <id> --confirm --json
mailbox cleanup --categories marketing --confirm --json # only one category
```
`cleanup` buckets each email into `protected_finance` / `protected_travel` / `security` /
`support_case` (never deleted) vs `marketing` / `routine_notification` (cleanup candidates) vs
`unknown`. Rules are sender/domain/subject based; override the allowlists via
`<configDir>/cleanup_rules.json`. The plan reports `by_category`, `candidates_by_category`, and
`protected_counts`; `--confirm` pipes the candidate categories into `email delete`.
### Discover the surface
```bash
mailbox account list --json
mailbox <cmd> --help --json # structured help: { name, description, options, arguments, subcommands }
```
## Token-saving tips
- **`--format compact` (global flag)** projects each email to just `{id, gid, account_id, folder, date, from, subject, unread, has_attachments, body_text_preview}` — the lightest useful shape for scanning, and it includes the 3-part `gid` so you can chain straight into `email show <gid>`. `--format jsonl` emits one JSON object per line (composable: `--format compact,jsonl`). `agent` is an alias of `compact`.
- **`--lean` (global flag, before subcommand)** strips ~10 noisy/duplicate top-level fields and per-email duplicates. Typical response shrinks by ~30%.
- **`--with-preview <N>`** on `email list / email search` fetches a body snippet alongside the envelope — saves one `email show` per email.
- **Batch `email show <gid1> <gid2> ...`** reuses one IMAP connection (and spans folders). Use it whenever you need ≥2 emails.
- **`gid`** (returned in every list/search/show response) is the global ID `account_id:folder:uid` — pass it instead of bare UID + `--account-id`, and `show`/mutate auto-target its folder.
- **Relative date shortcuts** (on `--since` / `--date-from`): `7d` (7 days ago), `3w`, `1mo`, `1y`, `12h`, `30m`, `today`, `yesterday`, `last-week`, `last-month`. ISO 8601 / `YYYY-MM-DD` still work.
- **`mailbox <cmd> --help --json`** returns a JSON descriptor of arguments, options, defaults — use to introspect any command instead of parsing human text.
## Output contract
- Every response: `success: boolean`. On failure: `error: string` + `error_code: string`.
- Common `error_code` values: `account_not_found`, `email_not_found`, `folder_not_found`, `invalid_argument`, `invalid_date`, `invalid_limit`, `ambiguous_account`, `size_limit`, `auth_failed`, `network_error`, `imap_error`, `smtp_error`, `operation_failed`, `unknown_error`.
- Exit codes: 0 success, 1 operation failed, 2 invalid usage.
- Every email object carries `gid` ("`<account_id>:<folder>:<uid>`"). Prefer it over bare `id`/`uid`.
- Batch `email show` returns `{ success, emails: [...], failed_ids: [{id, error, folder}], requested, returned }`.
- **Unread counts** on `list`/`recent` are three distinct fields — read the right one:
`unread_in_result` (unread among the rows actually returned — always trustworthy),
`folder_unread` (server count for the queried folder; `unread_count` is a back-compat alias),
`account_unread_total` (across all folders — `null` unless `--account-unread`), plus
`unread_as_of` + `from_cache` for snapshot freshness.
- **Cache freshness** is always reported on `list`/`recent` (including empty/compact results):
`from_cache` (bool), `cache_age_seconds` (how stale the snapshot is; `null` = live or unknown),
and, on a thin cached result, a `hint` string telling you to pass `--live`. So an empty list is
never a silent "nothing arrived" — check `from_cache`/`cache_age_seconds` before concluding.
- **Self-healing on a thin+stale cache**: when a cached `list`/`recent` comes back with fewer rows
than `--limit` AND the snapshot is older than the freshness window (default 120s, set via
`MAILBOX_CACHE_FRESH_SECONDS`; `0` disables), the CLI auto-falls back to a live IMAP fetch — so a
just-arrived OTP isn't missed between syncs. Pass `--live` to force IMAP outright.
- **Email body**: `body` (text), `body_source` (`text` | `html_derived` | `empty`), `html_body`
(empty unless `--full`/`--include-html`). HTML-only mail still yields a usable `body`.
- **Attachments**: each carries `is_signature` / `is_inline` / `is_real_attachment`;
`real_attachment_count` and `has_attachments` count only real attachments (an `smime.p7s`
S/MIME signature does not flip `has_attachments`).
- `--with-preview` adds `preview: string` and `preview_truncated: bool` per email.
- **Verification / OTP codes**: `email show ... --extract-code` scans subject+body and adds a
`codes: [...]` array (4–8 digit OTPs plus prefixed `LL-DDDDDD` forms like `QB-046193`). It's a
candidate list, ordered as found; the field survives `--format compact`.
## Safety rules
- Always pass `--json`. Always check `success`.
- Pass either a `gid` OR `--account-id <id>` for any per-email command.
- Mutating commands (`email send / delete / mark / move / flag`, `digest run`) default to **dry-run**; the agent must explicitly add `--confirm` after the user approves.
- Never leak account credentials in logs or model output.
## MCP server mode
Instead of shelling out to the CLI, an AI client can call mailbox tools
directly over MCP:
```bash
mailbox mcp config --json # prints an mcpServers entry to paste into the client config
mailbox mcp serve # run the server manually for testing (stdio)
```
16 tools registered: `account_list`, `account_test_connection`,
`email_list`, `email_search`, `email_show`, `email_folders`,
`email_mark`, `email_delete`, `email_flag`, `email_move`, `email_send`,
`sync_status`, `sync_force`, `inbox_organize`, `cleanup`, `digest_run`.
Each destructive tool defaults to dry-run; pass `confirm: true` to apply. `email_show` and the
mutate tools accept 3-part gids (`account_id:folder:uid`) and auto-target the gid's folder;
`email_list` exposes the `unread_in_result` / `folder_unread` / `account_unread_total` fields
and accepts `include_account_unread`; relative `date_from`/`date_to` shortcuts (`2d`/`3w`/…)
now work on the MCP path. `cleanup` returns a read-only plan unless `confirm: true`.
## Persistent daemon (5-30× faster CLI calls)
Each one-shot CLI invocation otherwise spends 1-3s on TCP+TLS+IMAP LOGIN.
With the daemon running, every CLI call reuses pooled connections, and
the daemon also runs a background SQLite sync so `email list` (without
`--live`) usually doesn't touch IMAP at all.
```bash
mailbox daemon install # autostart at login (macOS launchd / Linux systemd-user)
mailbox daemon status --json
mailbox daemon reload # drop pooled connections after editing auth.json
mailbox daemon stop
```
Set `MAILBOX_NO_DAEMON=1` to skip the daemon probe entirely.
Measured (Gmail INBOX, M2 MacBook over residential WAN):
| Operation | No daemon | Daemon (--live) | Daemon (cached) |
|---|---|---|---|
| Single `email list` | 5.0s | 1.0s | 0.17s |
| `email folders` | 5.0s | 0.85s | n/a |
| 5 sequential `email list` | 25s | 5.3s | **0.83s** |
| 3 parallel `email show` | ~15s | 2.7s | **0.88s** |
## Reference
- Repo: https://github.com/leeguooooo/Mailbox
- npm: https://www.npmjs.com/package/@leeguoo/mailbox-cli
- JSON contract docs: `docs/CLI_JSON_CONTRACT.md`
don't have the plugin yet? install it then click "run inline in claude" again.
drive the @leeguoo/mailbox-cli node cli to read and manage email across multiple imap accounts. handles listing, searching, reading, composing, and organizing messages in a single unified interface. use this when the user asks to read their email, search for a message, find an order confirmation, send a reply, delete spam, check unread counts, or sync a mailbox. returns a stable json contract with success: boolean, and on failure always includes error: string and error_code: string (machine-readable codes like account_not_found, auth_failed, network_error, etc.).
mailbox cli binary: prebuilt binary from github releases (no npm/node required). install non-interactively if missing:
curl -fsSL https://raw.githubusercontent.com/leeguooooo/Mailbox/main/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
minimum version: 2.11.0 (probe with mailbox --version before relying on --format compact|jsonl, email recent, cleanup, --since, --account-unread, --text-only, 3-part gid, or search --timeout).
auth credentials file: ~/.config/mailbox/auth.json (user must create and edit once). copy the example and fill in account details:
mkdir -p ~/.config/mailbox
cp $(mailbox --help 2>/dev/null | grep -o '.*/examples.*json' || echo '~/.local/bin/../lib/examples/accounts.example.json') \
~/.config/mailbox/auth.json
$EDITOR ~/.config/mailbox/auth.json
supports gmail, qq, 163, outlook, and any imap/smtp provider. no oauth tokens needed in most cases (app passwords or plain passwords work).
optional daemon (for 5-30x speedup): run once to start persistent connection pool:
mailbox daemon install
mailbox daemon status --json
skipped if MAILBOX_NO_DAEMON=1 is set.
account id: string identifying the configured account (e.g. gmail_work, qq_personal). list all with mailbox account list --json. required for most commands unless operating across all accounts (e.g. email recent).
gid (global id): unique identifier in format account_id:folder:uid (e.g. gmail_work:INBOX:12345). auto-returned in every list/search/show response. use it to target mutate operations without passing separate --account-id or --folder.
input: shell access, curl.
output: mailbox --version returns version string (e.g. v2.11.2).
run the install script:
curl -fsSL https://raw.githubusercontent.com/leeguooooo/Mailbox/main/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
mailbox --version
if command not found, the install failed or $PATH is stale. re-run the installer and verify ~/.local/bin is on $PATH.
input: user's email credentials (imap host, port, username, password or app password).
output: ~/.config/mailbox/auth.json file with valid account entries.
user edits ~/.config/mailbox/auth.json to add their accounts. test with:
mailbox account list --json
if the response is success: false with error_code: "auth_failed", the credentials are wrong. if success: true but the array is empty, no accounts are configured.
input: optional --account-id <id>, --limit N (default 20), --since <days>d (e.g. 7d = last 7 days).
output: json array of email objects, each with gid, date, from, subject, unread, body_text_preview (if --with-preview was passed).
list inbox for one account:
mailbox email list --account-id <id> --limit 20 --json
list recent across all accounts, newest-first:
mailbox email recent --limit 30 --json
with body preview (saves one follow-up email show call):
mailbox email list --account-id <id> --with-preview 200 --json
check the unread_in_result field (unread count among rows returned), folder_unread (server count for the folder), and account_unread_total (if --account-unread was passed, sums across all folders). also check from_cache, cache_age_seconds, and hint to know if the snapshot is stale.
input: --query "<text>" (subject and from on broken-imap servers like qq/163; full text on gmail), --from <sender>, --subject <substring>, --folder all|INBOX|..., --since <date>, --limit N, optional --timeout 60s.
output: json array of email objects (same shape as list).
simple search:
mailbox email search --query "interview" --since 2w --json
scoped search (faster on qq/163):
mailbox email search --from amazon --subject review --folder INBOX --account-id <id> --limit 20 --json
note: qq/163 have broken imap search, so the cli does client-side folder scans. --timeout (default 60s) is a hard wall-clock bound; if hit, response includes timed_out: true and pending_accounts list. text search only matches subject+from on qq/163/outlook (body text is skipped); use --from or --subject for predictable results.
input: gid (account_id:folder:uid) or list of gids, optional --full (raw html, no capping), --text-only (force text, strip html), --extract-code (scan for otp codes).
output: email object(s) with body, body_source (text|html_derived|empty), html_body (empty unless --full), attachments array (each with is_signature, is_inline, is_real_attachment), optional codes array (if --extract-code).
single email:
mailbox email show <gid> --json
batch (reuses one imap connection, spans folders):
mailbox email show <gid1> <gid2> <gid3> --json
with otp extraction:
mailbox email show <gid> --extract-code --json
html-only mail auto-converts to text; body_source indicates the source. if batch includes missing emails, response includes failed_ids: [{id, error, folder}].
input: --to <recipient>, --subject <subject>, --body "<text>", --confirm (required; defaults to dry-run).
output: dry-run json object with preview, or on --confirm, success: true and message id.
compose and preview:
mailbox email send --to alice@example.com --subject hello --body "how are you?" --json
apply (after user approves the preview):
mailbox email send --to alice@example.com --subject hello --body "how are you?" --confirm --json
input: gid or list of gids, --read or --unread, --confirm (required; defaults to dry-run).
output: dry-run json with groups breakdown (per account+folder, sample subjects), or on --confirm, success: true and count of affected emails.
mark single email as read:
mailbox email mark <gid> --read --confirm --json
bulk mark by sender (dry-run first):
mailbox email mark --from newsletter@shop.com --read --json
mailbox email mark --from newsletter@shop.com --read --confirm --json
input: gid or list of gids, --confirm (required; defaults to dry-run), optional --permanent (expunge; default is move to trash), --target-folder <name> (for move).
output: dry-run with groups and sample subjects, or on --confirm, success: true and count.
delete (move to trash):
mailbox email delete <gid> --confirm --json
permanently delete:
mailbox email delete <gid> --permanent --confirm --json
move to archive:
mailbox email move <gid1> <gid2> --target-folder Archive --confirm --json
bulk delete with filter (dry-run):
mailbox email delete --from spam@x.com --all-folders --json
--all-folders skips special-use folders (Sent, Drafts, Junk, Trash) by default; add --include-special to override. filters matching >100 emails require --confirm.
input: gid or list of gids, --set (flag) or --unset (unflag), --confirm.
output: dry-run or applied count.
mailbox email flag <gid> --set --confirm --json
input: --account-id <id>.
output: json array of folder objects with name, unread_count, total_count, special_use (e.g. "junk", "sent").
mailbox email folders --account-id <id> --json
input: --account-id <id>, optional --categories marketing|routine_notification (filter), --confirm (required; defaults to dry-run).
output: dry-run json with by_category, candidates_by_category, protected_counts, or on --confirm, applies deletions.
read-only plan (no changes):
mailbox cleanup --account-id <id> --json
apply (after user reviews):
mailbox cleanup --account-id <id> --confirm --json
only delete marketing emails:
mailbox cleanup --account-id <id> --categories marketing --confirm --json
cleanup sorts emails into protected_finance, protected_travel, security, support_case (never deleted), marketing, routine_notification (cleanup candidates), and unknown. rules are sender/domain/subject based; override via ~/.config/mailbox/cleanup_rules.json.
input: none.
output: json array of account objects with id, email, provider, imap_host, imap_port.
mailbox account list --json
input: --account-id <id>.
output: success: true and latency_ms if connection succeeds, or error with error_code.
mailbox account list --json
actually, use:
mailbox account test-connection --account-id <id> --json # if version >= 2.12
# fallback: mailbox email folders --account-id <id> --json (if folders list succeeds, auth is ok)
if mailbox --version returns command not found: cli is not installed. run the install script and add ~/.local/bin to $PATH. do not proceed with any email command until this passes.
if version is < 2.11.0: avoid --format compact|jsonl, email recent, cleanup, --since, --account-unread, --text-only, 3-part gid, and search --timeout. use fallbacks: --lean instead of --format compact, email list (all accounts) instead of email recent, --date-from instead of --since, --no-html instead of --text-only, 2-part gid (account_id:uid) instead of 3-part, and omit --timeout.
if account list --json returns empty array: no accounts are configured. user must edit ~/.config/mailbox/auth.json and add at least one account.
if success: false and error_code: "auth_failed": credentials in auth.json are wrong (bad password, expired app password, or wrong imap host). ask user to verify and update credentials.
if success: false and error_code: "network_error": imap server is unreachable (firewall, dns, timeout). check network connectivity and imap host/port in auth.json.
if email list / email recent returns from_cache: true and cache_age_seconds > 120: snapshot is stale (older than default freshness window). the response may be incomplete. either pass --live to force a fresh imap fetch, or note the hint field (if present) and re-run. self-healing kicks in if the cached result has fewer rows than --limit.
if email search --folder all times out (response includes timed_out: true): one or more accounts had slow imap scans (especially qq/163). check pending_accounts array for which ones timed out. re-run with --timeout <seconds> increased, or scope to --account-id <id> and/or --folder INBOX for snappier results.
if email search --query "<text>" matches nothing on qq/163: qq/163 imap search is broken; text search only matches subject+from. use --from or --subject filters instead, or switch to a gmail account.
if mutate command (email send / delete / mark / move / flag) returns a dry-run (no --confirm passed): always show the user the groups breakdown (which account+folder, sample subjects, count) before re-running with --confirm. this prevents surprise bulk deletes.
if mutate command matches >100 emails and --confirm is not passed: response is a dry-run preview, not an error. the >100 threshold requires explicit --confirm for safety.
if batch email show includes missing ids: response includes failed_ids array with {id, error, folder} tuples. do not assume all gids were found; check returned vs requested count.
if daemon status --json returns running: false: daemon is not running. either the user has not installed it, or it crashed. run mailbox daemon install and mailbox daemon status --json again. if it fails, check system logs or set MAILBOX_NO_DAEMON=1 to skip the daemon (slower but works).
if daemon is running but cli calls are still slow: daemon may be backed up or the system clock is wrong. try mailbox daemon reload to drop pooled connections and re-connect. if still slow, fall back to MAILBOX_NO_DAEMON=1.
if user wants to read email via mcp (not cli shell): switch to mcp mode. run mailbox mcp config --json to get the config snippet. client calls tools like email_list, email_search, email_show, email_mark, email_delete, email_send, etc. directly. mcp tools accept the same parameters (gid, account_id, folder, confirm, etc.) and return the same json schema.
every response includes success: boolean. on failure, always includes error: string (human-readable) and error_code: string (machine-readable).
common error_code values: account_not_found, email_not_found, folder_not_found, invalid_argument, invalid_date, invalid_limit, ambiguous_account, size_limit, auth_failed, network_error, imap_error, smtp_error, operation_failed, timed_out, unknown_error.
exit codes: 0 (success), 1 (operation failed), 2 (invalid usage).
every email object carries gid in the form account_id:folder:uid (e.g. gmail_work:INBOX:12345). use it instead of bare id/uid for downstream operations.
list / search responses:
success: booleanemails: [...] (array of email objects)unread_in_result: number (unread count among returned rows, always trustworthy)folder_unread: number (server count for the queried folder; legacy alias unread_count)account_unread_total: number | null (across all folders; only present if --account-unread was passed)from_cache: boolean (is this a cached snapshot?)cache_age_seconds: number | null (how stale; null = live or unknown)hint: string (optional; tells user to pass --live if snapshot is thin and old)timed_out: boolean (optional; only if search timeout was hit)pending_accounts: [string] (optional; account ids that timed out)requested: number, returned: number (batch operations)failed_ids: [{id, error, folder}] (batch operations; ids that failed)batch email show returns:
success: boolean