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

by Agent-First Kit Contributors

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.

You want your agent to work your inbox. So you hand it an IMAP client. Now every action your agent takes — opening a message, replying, archiving, deleting — happens against a live, mutable mailbox, through the same controls, with no boundary between “let me read this” and “I just moved 40 messages.” The read surface is a UI: sorting, threading, and selection state are behavior, not durable inputs. There is nowhere stable to write a case note or a draft reply. And after the fact, you can’t tell a suggestion apart from a mutation.

afmail takes the useful part of the git mental model and applies it to a mailbox: a remote, a local worktree, and an explicit push boundary. Files are the read interface; the CLI is the effect interface. Your agent reads and works entirely in local files, and the mailbox only changes when you say --confirm.

The model: pull, work locally, push deliberately

Each mailbox account is its own workspace directory — work inside it like a git worktree:

mkdir work-mail
cd work-mail
afmail init
afmail config set imap.host imap.example.com
afmail config set imap.username me@example.com
afmail config set imap.password_secret env:AFMAIL_IMAP_PASSWORD_SECRET
afmail pull
afmail status

afmail pull is like git pull: it reads your configured mailbox ids, imports message evidence, and refreshes generated read views. It is read-only IMAP — it does not mark messages seen, move them, tag them, delete them, append drafts, or send anything. Acquisition and effects are different verbs.

After a pull, the workspace separates active attention, archived work, machine evidence, queued effects, and audit history:

work-mail/
  triage/
    message_<id>.md          # generated read views — open them directly
  cases/open/
    <case_uid>-<name>/        # case.md, notes.md, messages/, drafts/, files/
  archive/
    cases/  notifications/
  .afmail/
    messages/                 # raw .eml, cleaned .txt, metadata .json
    push/                     # the queue of pending remote effects
    logs/events.jsonl         # append-only audit log

Your agent — or you — reads triage/message_*.md as plain Markdown. No live UI, no selection state, no API round-trip to look at mail.

The boundary is the point

Everything an agent does by default is local. Filing a case, writing a draft, archiving, marking spam — all of it mutates the workspace, not the mailbox.

afmail triage list
afmail case create --name "Customer issue" --message MESSAGE_ID --reason "needs follow-up"
afmail case c20260606001 reply MESSAGE_ID         # drafts a reply into the case — no network
afmail case c20260606001 draft validate reply-1
afmail case c20260606001 compose reply-1          # queues the outbound draft locally

None of that has touched the remote mailbox yet. The queued effects sit in .afmail/push/. To see them, you preview — bare push commands are dry runs:

afmail push list                 # inspect everything queued
afmail push archive              # preview the archive moves
afmail push drafts-send          # preview the outbound sends

The mailbox changes at exactly one moment: when you add --confirm.

afmail push archive --confirm
afmail push drafts-send --confirm

This is the line afmail draws and the reason it exists. In an agent workflow, “send”, “reply”, and “forward” mean local drafting unless you explicitly ask to push. The agent can classify, summarize, draft, and file all day; your mailbox stays untouched until a human confirms the remote effect. Every confirmed effect lands in .afmail/logs/events.jsonl, so a suggestion is never confused with a mutation after the fact.

Files are the read interface; the CLI is for effects

Each message is imported once as durable evidence: raw .eml, a cleaned .txt, and a metadata .json under .afmail/messages/. From those, afmail generates Markdown read views in triage/, cases/, and archive/. Refs are stable and meaningful: message_id for mail, case_uid (cYYYYMMDDNNN) for cases, archive_uid (aYYYYMMDDNNN) for direct archive categories. You can rebuild every generated view with afmail render refresh — a local-only operation that never contacts IMAP/SMTP or changes membership state.

Bring your own skills

afmail does not try to be the agent. It gives an agent skill a stable mailbox workspace to operate on — somewhere to read messages, write case notes, draft replies, and queue effects, all behind the push boundary. The workspace AGENTS.md holds mailbox-specific policy (priorities, reply style, escalation rules, labels). The installed skills/agent-first-mail.md is the reusable behavior contract for any agent driving the CLI.

afmail skill install
afmail skill status

Install

afmail is pre-release (v0.1.0) — build it from source:

cargo install --path spores/agent-first-mail
afmail skill install
afmail skill status

Restart your agent after installing the skill so it reloads afmail’s behavior rules.

Or just hand the project to your agent and let it decide. Paste this:

Read the Agent-First Mail README, skills/agent-first-mail.md, and docs/design-principles.md in this repo, then tell me in plain terms what it would do for me and whether it fits what I’m working on. If it’s a fit, build it from source after a quick review, then run afmail skill install so you follow its behavior rules.

Docs: github.com/agentfirstkit/agent-first-mail