When I stopped opening a mail client, I started opening a directory. The change happened the way useful changes usually do, by accident. I was scripting something, I wanted a single message, and I realized the message was just a file in a Maildir, sitting in cur/ next to all the others. That is the whole idea behind fem, and it is more radical than it sounds.

The last GUI I closed

The mail client was the last application I kept for what it was. Not a tool, an application: something you open, that opens windows, that runs a background daemon to keep an index warm, and that decides how attachments look and where search happens. It was a place, and email existed only inside it.

fem is the opposite bet. fem pull connects to the IMAP server, fetches unseen messages, and stores them in standard Maildir layout, a directory with cur/, new/ and tmp/, one file per message, each file a complete RFC 822 message. After the pull there is no state, no index, no daemon. There is a folder.

A maildir is not an application. It is a folder of files, which is the strongest format there is.

Files you can pipe

Once mail is files, it joins everything else. fem list prints a table, fem find searches by header, body or date, fem show prints one message, and fem push sends whatever it reads on stdin. Each command reads something and writes something, and because nothing is interactive, each command is a pure function of its inputs. Run it twice and you get the same answer.

That single property makes mail scriptable in a way a GUI never is:

# every fifteen minutes, quietly
*/15 * * * * fem pull --server imap.example.com --username [email protected] \
  --post-hook ~/bin/mail-notify.sh

# this evening's mail, as one stream
fem find --since 2026-05-01 --order-by date:asc

All of it is ordinary shell. None of it needs a client running. The same commands run in cron, in a script, or typed by hand, and they behave identically because they never waited on you.

Render everything, your way

fem show renders multipart mail by piping each part through a handler, and the handlers are yours to choose. text/plain goes to fem cat, which wraps lines and collapses citations; text/html goes to lynx -dump; anything else can get any tool you prefer. The mechanism is one repeatable flag:

fem show 176 --multipart-handle 'multipart/signed=contrib/pgp-signed-handler.sh'

A handler is just an executable. It reads the part on stdin, writes the rendered form to stdout, writes a short status line to file descriptor 3 and errors to stderr. Because a handler is any program, the decision about how a part or an attachment should look belongs to you, not to the mail client's developers. Prefer plain text and fall back only when no plain part exists; --multipart-preference text/plain,text/html sets that order. You are composing the client from pieces, which is what a CLI should feel like.

Hooks: the seams where you plug in

Handlers decide how mail is shown. Hooks decide what happens to mail while it flows. A pre-hook on fem pull receives the raw message on stdin, transforms it, and its stdout becomes the message that is stored; empty output skips the message, and a non-zero exit stops the pull. That is the seam for a malware detector, a spam filter you trust more than the server's, or a sanitizer that strips tracking pixels before anything is kept.

A post-hook receives the stored path as $1, which is the seam for a notifier daemon, a search index, or a backup. fem compose adds a hook on each side of the editor, which is how encryption and signing enter the flow without the tool knowing anything about them:

fem compose --post-compose-hook contrib/pgp-encrypt.sh
fem compose --post-compose-hook contrib/pqp-sign.sh

Pre and post hooks are binaries, not a plugin API. There is nothing to learn: whatever you can write to a file, you can run against a message. Post-corrections, virus scans, reformatting, all of it is just another hook in the pipeline.

Encrypted and signed, included

The source ships ready-to-use handlers for the two crypto worlds I care about. For PGP there is pgp-handler.sh and pgp-signed-handler.sh for reading, plus pgp-encrypt.sh and pgp-sign.sh for composing, all built on gpg and RFC 3156. For the post-quantum world there are PQP equivalents that use the pqp tool from my earlier essay: pqp decrypt, pqp verify, pqp encrypt and pqp sign.

The PQP handlers expect keys without a passphrase, because a hook runs with no TTY and cannot prompt. That constraint is not a limitation, it is the philosophy made concrete: everything in fem is designed to run without a human watching, which is exactly what makes it scriptable. Encrypted mail stops being a special state of the client and becomes one more content type with a handler attached.

What I gave up

The trade is real. No calendar, no address-book sync, no attachment previews in a window, no push notifications. Attachments are files again, which means you view them with whatever tool you already have, and if you do not have a handler for a type, you see it raw. You assemble the client yourself, and sometimes that means teaching a new tool how to behave.

But what I gained is the thing this site keeps coming back to: mail is plain files again, searchable by the tools I already trust, encrypted by handlers I can read, and readable thirty years from now.

The last GUI I closed stayed closed. I did not lose email, I relocated it, into a folder where it behaves like every other file I care about: mine, inspectable, and scriptable. The full source lives on GitHub.