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.