An Agentic Email Client
Friday, 20 March 2026 · 4 minsI built a terminal email client for Fastmail. It started as a stateless CLI for scripting and agent workflows, then grew a TUI with vim keys and an AI chat pane. It talks JMAP (RFC 8620, RFC 8621) directly.
Why
I am not very good at handling it email, it’s just too many emails and I feel overwhelmed by it. So I wanted an agent that could help me read and reply to messages, and web clients don’t have an API for that. IMAP is painful for programmatic access. Fastmail’s JMAP API is stateless and well-documented, so that’s what I built on.
The CLI came first, a tool an agent could shell out to. The TUI came later because I wanted to browse email without leaving the terminal. The chat pane came after that because I got tired of switching windows to talk to the agent.
The thin CLI: fm
fm is about 340 lines of Node.js. Stateless, no daemon, no local database. Each invocation hits the JMAP API, prints what it finds, and exits.
fm inbox [N] Recent inbox (default 10)
fm folder <name> [N] Recent emails from folder
fm mailboxes List all mailboxes
fm search <query> [options] Full-text search
--from, --to, --after, --before, --folder, --limit
fm headers <id> [id...] Headers + preview (no body)
fm read <id> Full email body
fm markread <id> [id...] Mark emails as read
fm open Launch TUI
fm send [options] Send email
fm reply <id> [options] Reply (threads properly)
The commands are layered on purpose. fm inbox 20 returns IDs and subjects. fm headers id1 id2 adds full headers and a preview. fm read id gives you the body. Each step narrows scope, and that matters for token cost. An agent scanning 20 emails with fm inbox burns maybe 50 tokens per message. fm headers costs around 200 each. Only the messages worth reading get the full fm read. A typical inbox pass costs a few hundred tokens instead of tens of thousands.
Search runs server-side via JMAP’s text filter, so there’s no local index and only matching messages come back. Same idea: don’t pay for what you don’t need.
The Agentic TUI
The TUI is Python, built with Textual. Textual gives you focus management, key routing, CSS layout, and async workers out of the box, so I didn’t have to wire any of that up myself.
The bottom pane is a chat interface. The agent sees which folder is selected, which email is open, and the body text. But the real value is what it can reach beyond the inbox. The agent subprocess is a full CLI chat session with access to tools; it can pull notes from my Obsidian vault, check today’s daily note for meeting context, or look up a topic I’ve written about before. When I’m replying to someone about a draft we discussed last week, the agent already knows what I said because it can search my sent folder and my notes in the same turn.
This is what makes it more than a mail client with a chatbot bolted on. The agent doesn’t just see the email in front of you, it sees your whole working context. A reply about a document review can reference the meeting notes where you discussed it. A follow-up on a thread can pull in what you wrote three weeks ago. The context is there because the agent has the same tools you do.
When you ask for a reply, the pane splits: chat on the left, compose editor on the right. The agent writes a draft in <reply> tags, which fills the editor. Edit it, ask for revisions, then Ctrl+D to send.
The agent runs as a subprocess that can fetch context from my vault. Other agents can use the CLI + the associated SKILL to get current context too fm inbox, fm search, fm read, anything that can shell out can read and reply to email without opening a browser.