xin is an agent-first command line tool for JMAP email (Fastmail-first).
- Default output is stable JSON (no flag needed).
Design goal:
xinis to JMAP email whatgog gmailis to Gmail. It is not a multi-provider wrapper and not a replacement forgog.
Homebrew (macOS / Linux):
brew install onevcat/tap/xinMinimal demo (Fastmail):
# This will bootstrap a minimal config automatically if missing.
xin auth set-token fmu1-xxxxx # Fastmail API token
xin search --max 1
# or: xin inbox next- Set your Fastmail API token (Bearer token):
# This command will bootstrap a minimal config automatically if missing.
# example token format: fmu1-xxxxx
xin auth set-token fmu1-xxxxx- Search and read (JSON by default):
xin search "from:alice seen:false" --max 10
xin get <emailId> --format full- Watch for changes (stream; default is NDJSON for agents):
xin watch --checkpoint /tmp/xin.watch.tokenIf you’re running from source, prefix each command with:
cargo run --bin xin --
# 1) Get current inbox (per-email)
xin messages search "in:inbox" --max 200 \
> /tmp/xin.inbox.json
# 2) Extract subjects (and emailId so an agent can act on it)
jq -r '.data.items[] | [.emailId, (.subject // "")] | @tsv' /tmp/xin.inbox.json
# 3) Filter by subject keyword (case-insensitive)
jq -r '.data.items[]
| select((.subject // "") | test("invoice"; "i"))
| {emailId, subject, from: (.from[0].email // null)}'
/tmp/xin.inbox.json# Archive MANY (example: all inbox items currently returned by search)
xin messages search "in:inbox" --max 200 \
| jq -r '.data.items[].emailId' \
| xargs -n 50 sh -c 'xin batch modify "$@" --remove inbox --add archive' _# Pick the next email to process (default: unread-only)
xin inbox next
# Apply an action (optionally for the whole thread)
xin inbox do <emailId> archive
xin inbox do <emailId> archive --whole-threadxin search uses a small sugar DSL (not Gmail-compatible). Common examples:
xin search "in:inbox" --max 20
xin search "in:inbox seen:false" --max 20
xin search "from:github subject:release" --max 10
xin search "has:attachment after:2026-01-01" --max 20
xin search "-in:trash" --max 20
xin search "or:(from:github | from:atlassian) seen:false" --max 20Full syntax and rules: see docs/CLI.md.
- Triage inbox:
xin messages search "in:inbox" --max 50→ list inboxxin inbox next→ pick the next email to process (default: 1)xin inbox do <emailId> <archive|trash|read|unread> [--whole-thread]
- Batch organize:
xin batch modify <emailId>... --add $seen --remove inboxxin thread modify <threadId> --add foo --remove $flagged
- Write:
xin send ...xin drafts create|rewrite|send ...
- Automation:
- Use
xin history/xin watch(JSON/NDJSON by default).
- Use
If you want a simpler, human-friendly view (e.g. to confirm a list before acting), add --plain.
It’s not a stability contract; agents should keep using the default JSON output.
xin --plain search "in:inbox" --max 20
xin --plain get <emailId> --format full- Default output is JSON (stable, agent-first contract; see
docs/SCHEMA.md). --plainis for humans (TSV / readable blocks). Don’t parse it in automation.
xin urlis Fastmail-only (generates a Fastmail web URL viaMessage-ID).- For other providers, xin surfaces
xinNotImplementedwhere appropriate.
- Initial spec (overview)
- CLI contract (full flags/commands)
- JSON output schema (agent-first contract)
- Implementation notes (RFC-first method plans)
Run all Rust tests:
cargo testRun Stalwart feature cases (e2e):
cargo run --bin xin-feature -- --fresh --case-dir tests/feature/stalwart/cases --all