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

Env vars only — no dotfiles.

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

Any of those can be overridden per-call with --url, --user, --password, --token, --account-id.

Commands

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-var config, no files. Like gh, like aws. Simpler to reason about.
  • 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.1.0.tar.gz (29.5 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.1.0-py3-none-any.whl (15.5 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for jmax-0.1.0.tar.gz
Algorithm Hash digest
SHA256 bcf38db4ee72444ce21e6dae7587df31b1e6a0b246f0347add8742f5de0f9406
MD5 6b2c8fc7b34f5515b550938d5144dc3a
BLAKE2b-256 dffe9fe1e0785554579dad37e256e6c02f4e0f917a312f7eae094333b02b9a9b

See more details on using hashes here.

Provenance

The following attestation bundles were made for jmax-0.1.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.1.0-py3-none-any.whl.

File metadata

  • Download URL: jmax-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 15.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.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 23d3e409f8c37215d1b5775cc170e028789a7a5577356e98093c80b67814af94
MD5 16e068dc84633a2b4bec3f0bd38c056f
BLAKE2b-256 bcaaecfe112064129b9615ab4ec49146a891e17485f76f294111b79953cdfb35

See more details on using hashes here.

Provenance

The following attestation bundles were made for jmax-0.1.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