# Announcing Postern, modern mail management
Saturday, 29. August 2026


I teased it before, the email solution I am working on.
I'm proud to release [Postern](https://code.raphting.dev/postern.git)!

## What is Postern?

[According to Wikipedia](https://en.wikipedia.org/wiki/Postern), a Postern is a secondary gate in a fortification.
Postern is for email self-hosters, and sits behind a professional email gateway and MX backup solution such as [p25.dev](https://p25.dev).

I just love the name Postern, because it combines the word "Post" with the idea of having a self-hosted email server
hidden somewhere on the internet. Postern allows inconspicuous entry of your emails, while the primary email server deals
with the troubles of port 25 traffic.

You can read emails and manage mailboxes over IMAP (IMAP4rev2), submit emails via SMTP Submission (even unauthenticated
locally for system messages) and receive emails over SMTP inbound.

Postern allows for simple user and address management. DKIM key management and key rotation is built in.

## Postern's Policy Engine

When you decide to self-host your emails, you are probably somewhat interested in ways to manage messages, such as
Spam or mailing lists.

Postern uses [Starlark](https://starlark-lang.org) to compute decisions for inbound and outbound messages.
The first feature I integrated is the "Gatekeeper". For every "From" email address you can persist a decision.
Don't want to see emails from this sender again? Move the message from the "Gatekeeper" mailbox into Spam or Trash.
Want to receive messages from this sender in your INBOX? Move the message anywhere else, and future messages will land
in your INBOX, not in the Gatekeeper folder.

That way, you have to sort through your Gatekeeper folder once in a while. But let's be realistic about the "Spam"
folder: How often do legitimate messages end up in Spam, and how often does Spam end up in your INBOX? I think it is
better to vet a Gatekeeper once in a while than continuously vetting Spam and INBOX simultaneously.

This is a long description for
[quite a short code snippet](https://code.raphting.dev/postern.git/blob/main/policy.star#L44),
which you can find in the project's repo.

The Policy Engine is not feature complete yet. I want to add HTTP calls so that external services can be involved in
managing email. That could be traditional Spam filters or even LLM integrations. You could even write your own
disposable mail service with "+" addressing (user+tag@example.com). Postern supports "+" addressing natively. That means
a registered address such as "user@example.com" will receive emails for "user+{tag}@example.com" as well.

What Postern does not do is rejecting or trashing messages. Each email should have the chance to end somewhere. As a
last resort in the "Junk" mailbox. You can use third party tools such as your email client to empty Junk after 30 days.

## How I use Postern already

My first use case is the Postern mailing list.
You can subscribe to postern+subscribe@lists.p25.dev to receive announcements.

I created a Postern Policy:

```Python
def on_message_deliver(conn, user, msg):
    if msg.header_to.address.startswith("postern-dev"):
        return "postern-dev"
    if msg.header_to.address.startswith("postern"):
        return "postern"
    return "INBOX"
```

This policy sorts messages starting with "postern" or "postern-dev" into their respective mailbox. Mailboxes that
don't exist will be created. Each email address (including "+" addressing) has their own mailbox which can be queried
separately later, even though I use the same IMAP account for simplicity.

With [fdm](https://github.com/nicm/fdm) I can trigger an action depending on the mailbox

```
action "pipe-postern" pipe "/usr/bin/mlmmj-receive -L /var/spool/mlmmj/postern"
action "pipe-postern-dev" pipe "/usr/bin/mlmmj-receive -L /var/spool/mlmmj/postern-dev"
```

And this is how I use Postern to sort messages into the correct mailbox and run a mailing list with mlmmj.

Postern supports local submission without authentication so that mlmmj can submit messages.

## Some architectural decisions

With Postern, I was pretty much able to design my dream Mail Delivery Agent.
Very high on my priority list was:

* Encrypted messages at rest
* Easy backups
* Policy Engine
* Easy user, address and domain (DKIM) management

Postern is written in Go so that it (cross-) compiles into a single binary, which makes server operations easy.

### Encryption

The first thing I always wanted for self-hosted email is encryption at rest for all messages.
Even though I trust my hosting providers enough to not actively sabotage me, storing emails on disk in plain text
is not a sane default. Backups can get lost, supervisor hosts or storage networks can get compromised.

If you host on Linux with a TPM, the Readme shows you how to create a masterkey with `systemd-creds` so that the
masterkey to all messages is properly encrypted on disk and only decrypted by systemd and the TPM.

I chose the [Data At Rest Encryption (DARE) format](https://github.com/minio/sio) and its well tested Go package.
As a compromise, the SQLite database is not encrypted (yet), so it can leak message metadata (To, From, Subject).
If those leaks were my concern, then email was probably not the right communication tool for me anyway.

Messages are gzipped before they get encrypted. Plain text messages are very well suited for compression, but not
after they got encrypted (compression is ineffective on ciphertext).

### Backups

There's not much to say really. All data goes into one single directory (with subdirectories),
including all messages, DKIM keys (encrypted) and the SQLite database. You can use any backup tool and storage
backend for backups.

### IMAP and SMTP, with TLS

I wanted to focus on the most supported protocols for email management first. As much as I like JMAP, email client
support lacks behind the support for IMAP. To reach the largest user-base, the IMAP4rev2 implementation is complete.
I have a comprehensive self-written test suite, explicitly closed source, so that future contributions are unbiased if
they choose to use LLM.
LLMs tend to cheat tests rather than implementing the protocol correctly.

SMTP does not have any special extensions enabled because Postern specifically lives behind p25.dev, Postfix, or any
other mature MTA. SMTP Submission has implicit TLS. SMTP Delivery requires STARTTLS and PLAIN authentication.
This is to enforce secure defaults as well as to prevent Postern running an open port 25. It is just not designed
for the wild west of port 25 SMTP communication and that in turn simplifies the hard parts of self-hosting email.

SMTP pass-through is worth mentioning: Whenever your email client submits a message, it is directly passed on to the
SMTP gateway. Only after successful upstream delivery is the message acknowledged. This concept simplifies Postern's
design: We avoid email queue management altogether - and that in return frees us from DSN generation.
I am not sure if pass-through will vanish. For a port 25 MTA, it would never work. But for Postern, I think the
simplicity of it justifies going slightly against the SMTP principles which delays delivery if the next hop is
temporarily down.

All connections for IMAP and SMTP require TLS. For now, it is your job to manage certificate retrieval and renewal.
One TLS certificate is enough to serve IMAP, SMTP Submission and SMTP Delivery.
Since we don't occupy ports 80 or 443 with Postern, you could even get creative with Caddy and the like. Otherwise,
[acme.sh](https://github.com/acmesh-official/acme.sh) is a good option.

## Development

You are invited to contribute to Postern!
Since [git supports email contributions natively](https://git-send-email.io/), and Postern is already perfectly suitable to run a mailing list,
I decided to develop Postern on my own git frontend "legit", plus running a development mailing list at
`postern-dev@lists.p25.dev` (subscribe with an email to `postern-dev+subscribe@lists.p25.dev`).

Just shoot an email to the list! I am not for any formalized communication. Treat it as a chat, simply say "hi", make
it yours! Posts are moderated for now just to see how it goes and to avoid Spam for subscribers. I plan to approve all
legitimate posts though.

What you can expect in the near future is:

* Easier CLI handling (currently, very reliant on systemd env vars)
* Extensions for the Policy Engine, such as HTTP calls and a key-value store
* Documentation beyond what is in the Readme
* Better integration with p25.dev

If there's any feature you miss, you are welcome to post to the mailing list!

If you made it this far, here is the link to [Postern](https://code.raphting.dev/postern.git) again.


By Raphael Sprenger
