Agent-First Mail

Let your AI agent work your inbox the way it works a git checkout — mail pulled into local files it reads, sorts, and drafts on your machine, and nothing reaches your real mailbox until you confirm the push.

Ask your agent: “Sort this week’s inbox and draft replies to anything urgent.”

Agent-First Mail — the afmail command — isn’t an email client, a helpdesk app, or an auto-replying bot. It’s the workspace your agent operates in, borrowed from the git shape: pull the mailbox into a local worktree, work in files, and push deliberately. IMAP pulls leave remote folders unchanged; Cloudflare pulls acknowledge spool objects after local import. Sending replies and changing IMAP folders still require a confirmed push.

Supported platforms: macOS, Linux, Windows. Works with Gmail, Fastmail, iCloud, other IMAP/SMTP mailboxes that accept password login, and Cloudflare Email Routing with an R2 spool; see Connect a Mailbox. Native JSON file channels also support non-email conversations such as web inquiries through the same case, draft, Review, and confirmed push workflow; see Message channels.

Try it in a minute

No credentials needed. demo init creates an ordinary workspace wired to a loopback mock mail server with a 12-message founder inbox: a reproducible product bug, receipts, an outage, a VIP request, phishing, and a fake invoice.

afmail demo init
cd afmail-demo
afmail demo serve

Leave demo serve running and ask your agent to pull and triage the inbox, draft replies, and show you the push preview. Pull and push use the real IMAP/SMTP paths, and a remote effect still requires the exact afmail push --confirm PLAN_ID the preview returned. Restart the server to reset it; delete afmail-demo/ and run demo init again for a clean slate.

One email, start to finish

You don’t run afmail — your agent does. You ask in plain language, and afmail is the tool it reaches for. You say:

You: Check my latest email, go through it, and handle anything urgent.

The agent starts read-only: it pulls the mailbox into local files, sees what came down, opens a case for a refund request, and reads what the customer actually wrote.

afmail pull                                              # bounded remote read → local files
afmail triage list                                       # locators for what came down
afmail case create --name "Refund request" --group support \
  --message message_inbox_88213_4 --summary "refund request for late order" --reason "wants a refund"
afmail message show message_inbox_88213_4                # order #4471, arrived late

To check whether the claim holds, it steps outside afmail to another skill — your store’s order system — and looks the order up. afmail neither knows nor cares what that skill is: it holds the mailbox while your agent brings the rest of the toolbox. Then it comes back with a recommendation:

Agent: One email needs a decision. A customer wants a refund on order #4471: it arrived 9 days late, it’s $79.90, and it’s refund-eligible. I’d refund it, reply to confirm, then archive the thread. Go ahead?

You: Ok, go ahead.

The refund goes through the store’s own API. Then the agent drafts the reply under your configured send identity and queues it — still local, nothing sent:

afmail case draft reply c20260606001 message_inbox_88213_4 --body-file refund-reply.txt
afmail case draft send c20260606001 reply-message_inbox_88213_4.md   # queue it
afmail case archive c20260606001 --reason "refunded order #4471, replied to customer"

Pushing is the moment your real mailbox changes, so the agent stops and shows you the draft and every other pending effect first:

Agent: Refund’s done. Here’s the reply before I send it:

Hi — sorry your order arrived late. I’ve refunded the full $79.90 to your original card; it should land in a few days. Thanks for your patience.

Send it and archive the thread?

You: Looks good — send it.

afmail push                                              # preview: the full plan and its plan_id
afmail push --confirm push_plan_<REVIEWED_PLAN_ID>       # apply exactly that plan

Then it reports back on everything else in the inbox — the look-alike PayPal domain to junk, the .zip “invoice” from a stranger to junk, the shipping notification and the Stripe payout to archive — and asks before filing any of it.

That is the whole shape: you ask, the agent works in local files, and nothing reaches your real mailbox until you’ve seen the resolved plan and it runs push --confirm with that exact ID.

Why it is shaped this way

The exact permission line between local reads, remote reads, local changes, remote effects, and permanent deletion is the authorization contract.

Connect your mailbox

One workspace directory per account. Your agent can run every step; the only thing it needs from you is an app password from your provider.

mkdir gmail && cd gmail
afmail init
afmail config set imap.host imap.gmail.com
afmail config set imap.username ada@gmail.com
afmail config set imap.password_secret "file:$HOME/.config/afmail/secrets.json#gmail_app_password"
afmail config set smtp.host smtp.gmail.com
afmail config set smtp.username ada@gmail.com
afmail config set smtp.password_secret "file:$HOME/.config/afmail/secrets.json#gmail_app_password"
afmail config add identities ada name="Ada Lovelace" email=ada@gmail.com default=true
afmail config remove identities me
afmail status          # what is still missing
afmail remote test     # logs in to IMAP and SMTP; sends nothing

For Gmail that app password comes from https://myaccount.google.com/apppasswords once 2-Step Verification is on, and Gmail needs a label named Archive because it has no archive folder of its own. Connect a Mailbox walks through Gmail, Fastmail, iCloud, and generic servers, where to keep the password so it stays out of your private Git history, and how mailbox folders map. Outlook.com and Microsoft 365 are not supported: Microsoft only allows OAuth logins, which afmail does not implement yet.

What your agent gets to work with

The Workspace Model and File Formats cover the exact layout.

Review with your agent

Some decisions want a screen, not a chat transcript. afmail triage review, afmail case review CASE_REF, and afmail case draft review CASE_REF DRAFT open a blocking, local human-in-the-loop page: the person reads the actual mail, settles each item on the spot, edits notes or a draft in place, and can type a question that reaches the agent on the command’s event stream. Closing the window is the ordinary end of a review; whatever was settled stays settled, whatever was not stays open. Review never files a message, queues a push, or touches the remote mailbox — settling records a decision, and the normal preview/confirm flow still applies.

--mode link hands the same page to a phone on your trusted local network; --mode session registers it for an Agent-First UI session to pick up later. Review pages render from the same editable templates as generated Markdown; see the template contract.

Typed local HTTP API

Trusted agent runtimes and applications can drive afmail over HTTP through the same domain core. afmail api serve publishes OpenAPI 3.2 plus standalone JSON Schemas and reuses the CLI’s locks, idempotency records, audit behavior, and push plan confirmation. It exposes no shell or generic command endpoint. See the HTTP API contract.

Adopt it: hand afmail to your agent

The quickest way to find out whether afmail earns a place in your toolkit is to let your agent read it and tell you. Paste this to your agent:

Read what Agent-First Mail is at https://agentfirstkit.com/agent-first-mail and tell me in plain terms what it would do for me. If I want it, install it — the prebuilt package for the quick path, or build from source after a quick security review of the repo if you’d rather read what you run — then run afmail skill install so you follow its behavior rules. Then set up my mailbox: afmail uses one workspace directory per account, so make a folder, run afmail init in it, and help me connect my mailbox following https://agentfirstkit.com/agent-first-mail/docs/setup/.

If it’s a fit, install it — a prebuilt package, or from source if you want to read it first:

# prebuilt binary
brew install agentfirstkit/tap/afmail   # macOS / Linux
scoop bucket add agentfirstkit https://github.com/agentfirstkit/scoop-bucket && scoop install afmail   # Windows

# or build from source after reviewing the repo
git clone https://github.com/agentfirstkit/agent-first-mail
cd agent-first-mail
cargo install --path . --bin afmail --locked --force

Prebuilt archives are also available from GitHub Releases. Default builds include the demo, api, review, cloudflare, and smtp features; cargo build --no-default-features gives a core mail-workspace binary whose excluded commands return a structured feature_disabled error.

Then install the embedded Agent Skill so the agent follows afmail’s behavior rules, and restart the agent so it reloads them. skill install targets Codex, Claude Code, opencode, and Hermes; skill status reports whether each install is present, valid, and current:

afmail skill install
afmail skill status

To remove it, run afmail skill uninstall.

Docs

Agent-First Mail v0.12.0: Where the Link Goes

HTML mail used to reach the Review page as walls of tracking URLs or as text with every link target stripped. It is read through a Markdown copy now, and every link says which domain it goes to and whether that is the sender's. Plus a cleared Cc that was still sent, a move that waited forever for a search index, and a rejection that is no longer mistaken for an unknown.

Agent-First Mail v0.11.0: Nothing Waits for Done

Review used to stage a person's answers and commit them all at once when they pressed Done, which meant closing the window threw the work away. Each item now settles the moment it is decided, the workspace is the source of truth rather than the process, and a draft can no longer attach a file it merely points at.

Agent-First Mail v0.10: Not Our Window

v0.10 hands the human review window to agent-first-ui: afmail deletes its own listener, request parser, and browser launcher, and gains what it never had — four independently replaceable review surfaces, a frontend contract with a version on it, and doctor checking the UI a person will actually see. Credentials in config move to one key with the AFDATA source grammar.

Agent-First Mail v0.9: Every Legal Combination

v0.9 compiles the whole afmail CLI from a closed registry — 106 commands, 140 legal argument shapes, one --help call to see all of a command's at once — and hardens what the agent acts on underneath it: a message's identity is now its bytes, a push refuses stale UIDs, and an edit to a file you wrote moves only the keys that changed.

Agent-First Mail v0.8: Ask the Human

v0.8 brings the human decision inside afmail: interactive review windows for triage, cases, notifications, and drafts, opened by the agent and answered by you — rendered from the same typed documents the agent reads, through one MiniJinja template system you can edit and translate.

Agent-First Mail v0.7: Run It Again

v0.7 makes every mutation safe to retry: a --request-id turns any local change into an idempotent operation, remote effects only fire from a plan you already reviewed, and pulls run inside a byte-and-count budget you set up front.

Agent-First Mail v0.6: Fewer Doors

v0.6 collapses the separate archive subcommand into case, replaces archive/reopen cycles for contacts and cases with explicit delete, and splits message restore into unspam and untrash — fewer places to look, more precise actions.

Agent-First Mail v0.5: Notifications Aren't Cases

v0.5 splits the two things afmail used to call 'archive': finished cases stay in the archive, but standalone notification and reference mail now files into its own notification collections — and every case has to name the queue it belongs to.

Agent-First Mail v0.4: The Workspace Speaks Your Language

v0.4 localizes the part of afmail you actually read: every generated Markdown view and workspace scaffold now ships in nine fully-translated language packs, chosen by one config line — while the CLI, the wire format, and the one push boundary stay exactly where they were.

Agent-First Mail v0.3: The Workspace Knows Who

v0.3 gives the mailbox workspace a sense of who: contact cards that auto-link to cases and materialise onto message views, and an identity registry of named send personas your drafts can write footers from — both still behind the one push boundary.

Agent-First Mail v0.2: One Push, All or Nothing

v0.2 tightens the push boundary into a single all-or-nothing operation, adds a draft preview, unifies message restore and lifecycle-agnostic case reads, lets you archive a message directly, and lets a workspace live in a subdirectory.

Agent-First Mail v0.1: Your Inbox as a Git Worktree

Agent-First Mail gives each mailbox a local, file-first workspace: pull mail into files, work cases and drafts offline, and push remote effects only when you explicitly confirm.