Recipes
mxr is a Unix citizen. Most list/search commands support --format json|jsonl|ids|csv|table; the core mail mutations support --dry-run and accept either explicit IDs, --search QUERY, or piped IDs on stdin. The exact per-command capabilities live in the automation contract; the JSON shapes are documented in JSON output schemas. This page is the cookbook.
Each recipe shows three things:
- Situation — the actual problem you’re solving.
- Pipeline — copy-pasteable, no shell prompt prefix.
- What you get — the shape of the output.
If you’d rather hand the situation to an LLM, every section ends with a Tell an agent block — a natural-language prompt that maps cleanly onto the same pipeline.
With fzf — interactive picker
Section titled “With fzf — interactive picker”Pick a thread to read
Section titled “Pick a thread to read”Situation: too many unread messages to scroll, but you know it’s “from someone at acme”.
mxr search 'is:unread from:acme' --format jsonl \ | jq -r '"\(.message_id)\t\(.from)\t\(.subject)"' \ | fzf --delimiter='\t' --with-nth=2,3 \ | cut -f1 \ | xargs -I{} mxr cat {} --view readerWhat you get: an interactive list keyed by sender + subject; pressing Enter prints the chosen message body (rendered with reader mode) to your terminal. Use --view raw for the unrendered body, --view html to dump the original HTML.
Pick a draft to resume
Section titled “Pick a draft to resume”mxr drafts --format jsonl recover \ | jq -r '"\(.draft_id // .id)\t\(.subject // "(no subject)")"' \ | fzf --delimiter='\t' --with-nth=2 \ | cut -f1 \ | xargs mxr drafts resumeBrowse senders by volume
Section titled “Browse senders by volume”mxr storage --by sender --format jsonl \ | jq -r '"\(.bytes)\t\(.key)"' \ | sort -rn \ | fzf --header='bytes | sender' \ | awk '{print $2}' \ | xargs mxr senderWhat you get: pick the heaviest senders interactively; Enter drills into their full profile (volume, cadence, open commitments).
Tell an agent"Help me find the sender I email most often that I haven't replied to in 30 days. List the candidates first; I'll pick one."With jq — filter and reshape JSON
Section titled “With jq — filter and reshape JSON”Daily digest of unread
Section titled “Daily digest of unread”mxr search 'is:unread newer_than:1d' --format json \ | jq -r 'group_by(.from) | map({sender: .[0].from, count: length, latest: max_by(.date).subject}) | sort_by(-.count) | .[] | "\(.count)\t\(.sender)\t\(.latest)"'What you get: a per-sender digest, descending by message count, with the most recent subject for context.
Find threads that need follow-up (no reply in 7 days)
Section titled “Find threads that need follow-up (no reply in 7 days)”mxr stale --theirs --older-than-days 7 --format json \ | jq -r '.[] | "\(.thread_id)\t\(.latest_subject)\t\(.latest_date)"'--theirs means they owe me a reply (latest message is outbound).
--older-than-days 7 excludes threads with activity in the last week.
Extract all attachment filenames from a query
Section titled “Extract all attachment filenames from a query”mxr search 'has:attachment from:billing' --format ids \ | while IFS= read -r id; do mxr attachments list "$id" doneTell an agent"Summarize my unread mail from the last 24 hours grouped by sender. Use `mxr search 'is:unread newer_than:1d' --format json` and group with jq."With xargs — bulk operations
Section titled “With xargs — bulk operations”Archive everything matching a search
Section titled “Archive everything matching a search”mxr search 'from:no-reply@*.example.com older_than:30d' --format ids \ | mxr archive --yes--yes skips the confirmation prompt; needed when stdin is not a terminal.
Trash a sender’s entire backlog
Section titled “Trash a sender’s entire backlog”mxr search 'from:spam@example.com' --format ids \ | mxr trash --yesApply a label to a query, in parallel
Section titled “Apply a label to a query, in parallel”mxr search 'from:billing@*.example.com' --format ids \ | mxr label billing --yesFor mxr-on-mxr bulk actions, prefer piping IDs directly into the mutation. Use GNU parallel only when you fan out non-mxr work.
Dry-run before committing
Section titled “Dry-run before committing”Always preview when piping into mutations:
mxr search 'from:no-reply' --format ids \ | mxr archive --dry-runThe core mail mutations (archive, trash, spam, snooze, label, etc.) support --dry-run and print what would happen without touching the provider.
Tell an agent"Archive every unread newsletter older than 30 days. Show me the dry-run first; I'll approve."With watch — live dashboards
Section titled “With watch — live dashboards”Live unread count
Section titled “Live unread count”watch -n 30 'mxr count is:unread'Sender activity heatmap
Section titled “Sender activity heatmap”watch -n 60 'mxr search "newer_than:5m" --format json \ | jq -r ".[] | .from" | sort | uniq -c | sort -rn | head'What you get: a refreshing top-10 of senders pinging you in the last 5 minutes — useful during incidents.
Sync health monitor
Section titled “Sync health monitor”watch -n 5 'mxr sync --status --format table'Tell an agent"Tell me when @ceo emails me. Poll every 30 seconds with `mxr search 'from:ceo@example.com is:unread' --format ids` and ping me on stdout when the result is non-empty."With cron / systemd — scheduled work
Section titled “With cron / systemd — scheduled work”Morning digest at 09:00
Section titled “Morning digest at 09:00”crontab -e:
0 9 * * 1-5 /usr/local/bin/mxr search 'is:unread newer_than:1d' \ --format json | mail -s "Morning digest" you@example.comAuto-snooze low-priority newsletters until weekend
Section titled “Auto-snooze low-priority newsletters until weekend”0 17 * * 5 /usr/local/bin/mxr search 'label:newsletters is:unread' \ --format ids | /usr/local/bin/mxr snooze --until 'monday 9am' --yessystemd user timer
Section titled “systemd user timer”~/.config/systemd/user/mxr-cleanup.service:
[Service]Type=oneshotExecStart=/bin/sh -c 'mxr search "from:no-reply older_than:90d" --format ids | mxr archive --yes'~/.config/systemd/user/mxr-cleanup.timer:
[Timer]OnCalendar=Sun *-*-* 02:00:00Persistent=true
[Install]WantedBy=timers.targetsystemctl --user enable --now mxr-cleanup.timerTell an agent"Set up a weekly cron that archives no-reply mail older than 90 days. Show me the cron line; don't install it."With $EDITOR — compose loops
Section titled “With $EDITOR — compose loops”Reply to a search result interactively
Section titled “Reply to a search result interactively”mxr search 'from:alice' --format ids \ | xargs -I{} mxr cat {} --view reader \ | $EDITOR - # paste content into editor as scratchOpen a draft directly in your editor (no daemon)
Section titled “Open a draft directly in your editor (no daemon)”mxr compose already opens $EDITOR with the markdown + frontmatter shell. To prepare a one-off reply from a script:
mxr reply MESSAGE_ID --body-stdin <<'EOF'Hey — quick yes from me. Will follow up tomorrow with the deck.EOFWalk the reply queue
Section titled “Walk the reply queue”mxr replies --format ids | while read id; do mxr cat "$id" --view reader echo "Reply? [y/N/q]" read answer < /dev/tty case "$answer" in y) mxr reply "$id" ;; q) break ;; esacdoneWith grep / ripgrep — content search across exports
Section titled “With grep / ripgrep — content search across exports”# Export everything from a sender, search the corpus locallymxr search 'from:legal@example.com' --format ids \ | while IFS= read -r id; do mxr export "$id" --format markdown; done \ | rg -i 'NDA|confidential|terms'# Fast full-text grep over message bodies via Tantivymxr search 'NDA OR "non-disclosure"' --format jsonl \ | jq -r '.subject + " — " + .from'The second form is ~100× faster because it stays inside the search index. Use the first only when you need regex or ripgrep features the search grammar doesn’t support.
With parallel — fan-out work
Section titled “With parallel — fan-out work”Fetch bodies for many threads concurrently
Section titled “Fetch bodies for many threads concurrently”mxr search 'from:billing' --format ids \ | parallel -j8 mxr cat {} --view reader \ | rg -i 'amount due|invoice' \ | headPer-account sync in parallel
Section titled “Per-account sync in parallel”mxr accounts --format ids \ | parallel -j4 mxr sync --account {}Talking to your agent
Section titled “Talking to your agent”mxr is designed so agents can run it directly. Three rules keep the interaction safe:
- Read-only first. Have the agent run
mxr search,mxr cat,mxr stale,mxr storage --by sender,mxr sender,mxr summarizebefore any mutation. - Always
--dry-runbefore bulk mutations. Every mutation supports it; the agent should preview the affected IDs and report them back to you. - Use
--format jsonor--format jsonlwhen piping into the agent’s reasoning loop, nevertable(that’s for humans).
Prompt patterns that work
Section titled “Prompt patterns that work”- “Show me the 10 senders I owe replies to. Use
mxr staleandmxr senderto verify cadence.” - “Find every newsletter from this month, group by sender, and propose a label rule for the noisiest three.”
- “Draft a polite decline to the latest message from acme.com, but show me the draft before sending.”
- “Summarize unread mail from the last 24h grouped by importance. Use
mxr summarizeonly on threads with 4+ messages.”
Read-only fast paths agents should know
Section titled “Read-only fast paths agents should know”mxr search '<query>' --format json # full searchmxr cat MESSAGE_ID --view reader # rendered, distraction-freemxr summarize THREAD_ID # LLM 2-3 sentence summarymxr sender alice@example.com # per-sender aggregatesmxr stale --mine --older-than-days 7 --format jsonmxr count <query> # count without payloadmxr drafts --format json # what's waitingmxr doctor --format json # daemon healthMutating fast paths to gate behind review
Section titled “Mutating fast paths to gate behind review”mxr archive ID --dry-run # previewmxr archive --search '<query>' --dry-run --yesmxr label <name> ID --dry-runmxr snooze ID --until '...' --dry-runmxr send DRAFT_ID --dry-run # preview sendmxr screener allow|deny|feed|paper-trail <addr>The agent doesn’t need a special API. The same flags humans use are the same ones it composes.
See also
Section titled “See also”- CLI reference — every command and flag.
- Automation contract — which commands support
--format json,--dry-run, stdin IDs. - JSON output schemas — canonical field names for piping into
jq. - HTTP bridge — same surface over HTTP for desktop / web / mobile clients.
- API explorer — interactive Scalar reference; try requests against your local daemon.
- For agents — boundaries and safe defaults when an LLM is driving.
- AI agent skill — install the mxr skill into Claude / Cursor / Continue.