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

by Agent-First Kit Contributors

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.

afmail is a filing system your agent works in. v0.1 drew the line — the agent reads and sorts local files, and the real mailbox only changes on --confirm. v0.2 made every queued effect one all-or-nothing push. v0.3 gave the workspace a sense of who. v0.4 taught it to render in the language you read. v0.5 sharpens the one thing all of that depends on: where a message goes when it’s no longer in triage.

One word was doing two jobs

A message leaves triage in one of a few directions. Most that matter become a case — a folder per ongoing issue, holding the thread, notes, and draft replies. But a lot of mail isn’t an issue at all: a receipt, a shipping notice, a policy update, a “your report is ready.” It needs to be filed and kept, not worked. Through v0.4, afmail filed that standalone mail into “direct message archive categories” — and it also parked finished cases in the archive. Two genuinely different ideas, both spelled archive.

That overload leaked everywhere: into the command surface (archive message create for a receipt, archive case for a closed thread), into the paths, into the agent’s triage decision. v0.5 separates them.

Standalone reference and notification mail now files into its own surface:

afmail notification create --name "Order receipts" \
  --message message_inbox_90233_7 --summary "receipt for order #4471" --reason "keep for records"
afmail notification list
afmail notification show n20260701001

Notification collections get their own stable ids (nYYYYMMDDNNN), their own notifications/<uid>-<name>/notification.md read views, and their own notification list/show locators. The archive command now means exactly one thing: cases you’ve finished with. Nothing else lives there.

A case has to name its queue

The other half of the split is on the case side. A case exists to be worked, which means it belongs to some queue — support, invoices, legal, a bug tracker, whatever your mailbox actually runs on. Through v0.4 the group was optional, and an optional group quietly became a junk drawer: everything landed in one undifferentiated pile.

v0.5 makes the category mandatory:

afmail case create --name "Refund request" --group support \
  --message message_inbox_88213_4 --summary "refund for late order" --reason "wants a refund"

There’s no default and open is not accepted as a catch-all — the agent has to choose a meaningful, path-safe category in the mailbox’s own language (support, invoice, 发票, 合同, or whatever the local taxonomy is). That category is load-bearing: archived cases now nest under archive/cases/<group>/, so the queue a case belonged to survives after it’s closed instead of dissolving into a flat pile of finished work.

The triage decision, stated plainly

Together the two changes give the agent a clean fork at triage time:

One fork, two destinations that no longer share a name.

Sharper edges on the boundary

v0.5 also tightens two safety edges around that boundary:

None of this moves the line v0.1 drew. The push is still one all-or-nothing operation, drafts still show their footers before they leave, and the mailbox still changes only when you say so. v0.5 just makes sure that when the agent files something, it files it in the right place — and can tell you which.

Install

brew install agentfirstkit/tap/afmail        # macOS / Linux
scoop bucket add agentfirstkit https://github.com/agentfirstkit/scoop-bucket
scoop install afmail                          # Windows
cargo install agent-first-mail                # any platform

After installing, run afmail skill install so your agent reloads afmail’s behavior rules.