Skip to main content

Agent-friendly JMAP email CLI. Flat verbs, JSON by default, exit codes that mean something.

Project description

jmax

Agent-friendly JMAP email CLI. Flat verbs, JSON-by-default, exit codes that mean something.

It exists because every other "mail CLI" is either Fastmail-only, written for humans with a pager and a color scheme, or shipped as part of a mail client. None of them are good for scripting or for AI agents. jmax is.

Install

uv tool install jmax
# or
uvx jmax inbox

Python 3.10+. Only runtime dependency is httpx.

Configure

Two ways. Both work. Pick whichever fits.

Interactive (recommended for humans):

jmax auth login https://mail.example.com --user you@example.com
# prompts for password; writes ~/.config/jmax/auth.json at mode 0600

Non-interactive variants: --password-stdin, --token-stdin, --token, --account-id, or --verify to hit the server before saving.

Env vars (recommended for agents / CI):

export JMAP_URL=https://mail.example.com
export JMAP_USER=you@example.com
export JMAP_PASSWORD=...                           # or JMAP_TOKEN for bearer auth
# optional:
export JMAP_ACCOUNT_ID=...                         # auto-discovered if omitted

Precedence: CLI flag > env var > auth file. Overriding a single value per call with --url / --user / --password / --token / --account-id works as you'd expect.

Inspect what's active with jmax auth status. Clear it with jmax auth logout.

The auth file is plaintext JSON at 0600. No keyring, no obscuring, no false sense of security — if you don't like that, use env vars and keep the secret in your OS keyring (e.g. JMAP_PASSWORD=$(security find-generic-password -w -s jmax)).

Commands

jmax auth login <url> [--user U] [--password-stdin | --token-stdin | --token T]
jmax auth logout
jmax auth status
jmax inbox [--unread] [--limit N] [--since 7d|24h|ISO] [--count]
jmax mailboxes
jmax search <query> [--from X] [--in MAILBOX] [--unread] [--since ...]
jmax get <id> [--headers | --body | --attachments | --subject]
jmax read <id>...
jmax unread <id>...
jmax flag <id>...
jmax unflag <id>...
jmax move <id>... --to <mailbox>
jmax trash <id>...
jmax delete <id>... [--permanent] [--yes]
jmax send --to X --subject Y [--body-file F | stdin]
jmax reply <id> [--body-file F | stdin]
jmax attachment <email-id> <filename> [-o FILE]

All commands accept --table (where applicable), --dry-run (where mutating), and --yes (where destructive).

Output contract

  • Scalars → plain stdout. jmax inbox --count prints 42. jmax get e1 --subject prints Hello.
  • Lists/objects → JSON by default. Pipe into jq, loop over it, assert on it.
  • Mutations → silent on success. Noise is a bug.
  • --table → human-readable table for lists. Opt-in, never the default.

Exit codes

Code Meaning
0 Success
1 Ran fine, returned no results or the ID wasn't there
2 Something broke — auth, network, bad args, server error

So jmax search urgent && echo 'got some' works the way you'd hope.

Safety

Destructive operations refuse to run without --yes:

  • jmax delete --permanent
  • jmax delete / jmax trash on more than 10 IDs at once

Every mutating command supports --dry-run, which prints the raw JMAP methodCall instead of executing. Hand that to an agent when you want it to "show its work" before the real run.

Recipes for agents

# How many unread emails in the last day?
jmax inbox --unread --since 24h --count

# Dump every sender from this month into a CSV:
jmax search "" --since 30d --limit 1000 | jq -r '.[] | .from' | sort -u

# Archive every newsletter:
jmax search "" --from "newsletter@" --limit 500 \
  | jq -r '.[].id' \
  | xargs -n50 jmax move --to Archive

# Show subject, then reply:
jmax get $ID --subject
echo "Thanks!" | jmax reply $ID

Design principles

  • Do one thing per command. No multiplexed "smart" verbs.
  • Trust the server. JMAP queries are cheap; there's no local cache, no sync, no database. Every invocation re-queries. The agent's memory is the agent's problem.
  • Don't pretty-print by default. The default consumer is a pipe.
  • Env vars win, a plaintext file fills the gaps. Stealing from llm and mc — a single managed JSON file at ~/.config/jmax/auth.json, not a TOML you hand-edit. Env vars always override.
  • Fail loud. No silent retries on auth failure. Fix the env var.

Supported servers

Built against Stalwart and Fastmail. Any RFC 8620 / RFC 8621 server should work — file an issue if yours doesn't.

License

MIT.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jmax-0.2.0.tar.gz (34.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jmax-0.2.0-py3-none-any.whl (19.5 kB view details)

Uploaded Python 3

File details

Details for the file jmax-0.2.0.tar.gz.

File metadata

  • Download URL: jmax-0.2.0.tar.gz
  • Upload date:
  • Size: 34.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for jmax-0.2.0.tar.gz
Algorithm Hash digest
SHA256 31f2a4dc1b051b5f17ac7d80bef91abdcc579d60c7c818c359e29f39ffbdc550
MD5 3c91efdec0f28e9a60b4ce759f029237
BLAKE2b-256 c09a0658c36eb6f8d652a406363b8bffc3caf878718d44c0989f6b9e8009bbba

See more details on using hashes here.

Provenance

The following attestation bundles were made for jmax-0.2.0.tar.gz:

Publisher: release.yml on amarcin/jmax

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jmax-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: jmax-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 19.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for jmax-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4587eaa55a472558b793804fd5480e06a941c75e32311b7a391a4b76f1a96a9c
MD5 c539a74004a8646cd532ad85b1b22d27
BLAKE2b-256 d08f3f89ad2542494c70b637f0e6c5cb6df84a15a4a0e583490f0209f3a11a6d

See more details on using hashes here.

Provenance

The following attestation bundles were made for jmax-0.2.0-py3-none-any.whl:

Publisher: release.yml on amarcin/jmax

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page