Metadata-Version: 2.4
Name: jmax
Version: 0.2.0
Summary: Agent-friendly JMAP email CLI. Flat verbs, JSON by default, exit codes that mean something.
Project-URL: Homepage, https://github.com/amarcin/jmax
Project-URL: Issues, https://github.com/amarcin/jmax/issues
Author: Augustin Marcin
License: MIT
License-File: LICENSE
Keywords: agent,cli,email,fastmail,jmap,stalwart
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications :: Email
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Description-Content-Type: text/markdown

# 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

```bash
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):**

```bash
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):**

```bash
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

```bash
# 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](https://stalw.art) and [Fastmail](https://www.fastmail.com). Any RFC 8620 / RFC 8621 server should work — file an issue if yours doesn't.

## License

MIT.
