PRODUCT HUNT

Klar launches on Product Hunt October 7.

Follow along

Klar for Stalwart

A transformer spam filter that runs on your Stalwart server. Nothing leaves the box.

curl -fsSL https://raw.githubusercontent.com/klar-im/engine/main/install.sh | sudo KLAR_ACCEPT_MODEL_LICENSE=1 sh

Linux with systemd, on x86_64 or arm64. Older systems run the container.

How it fits into Stalwart

Stalwart hands each inbound message to the Klar milter at the DATA stage. The engine reads the headers and the body on your server. It writes its verdict back as X-Klar headers.

A Sieve script then files each message on that verdict. Spam goes to Junk, marketing to its own folder, and the rest stays in the inbox.

One more command points Stalwart at the milter. It adds the milter for inbound mail, sets strict DMARC on port 25 and installs the Sieve script. With --shadow, the built-in filter keeps scoring but files nothing:

export STALWART_URL=http://127.0.0.1:8080 STALWART_USER=admin STALWART_PASSWORD=...
python3 /opt/klar/share/stalwart/scripts/apply.py --shadow --milter-host 127.0.0.1 --milter-port 8891

The built-in filter has to stop deciding. It runs before any milter, and a message it marks as spam goes to Junk whatever Klar says.

Then turn filing on for each mailbox people read, with that account's own password. Leave it off spam traps and abuse@: they keep the headers and nothing moves.

STALWART_PASSWORD='alice-pw' python3 /opt/klar/share/stalwart/scripts/sieve_activate.py --url https://mail.example.com --user alice@example.com
The operator guide

Compare before you switch

In shadow mode Stalwart's filter keeps running. It scores every message and writes the result into the headers, but it never files anything. Klar decides where mail goes.

So every message carries two verdicts. Count where they disagree, on your own mail, for as long as you like. When you are ready, run the command again without --shadow. That turns the built-in filter off:

python3 /opt/klar/share/stalwart/scripts/apply.py --milter-host 127.0.0.1 --milter-port 8891

You can also leave it on for good. A second opinion on every message costs a few milliseconds.

internet SMTP :25
Stalwart DATA stage
built-in filter, in shadow scores every message, files nothing X-Spam-Result: KLAR_SHADOW (-100), …
mailbox the account’s Sieve files on the verdict
regular → Inbox marketing → Marketing spam → Junk
klar-milterd the Klar engine, on the same box
reads headers and body, classifies locally a multilingual transformer, no network, no message kept
what the milter adds X-Klar-Label: spamX-Klar-Class: spamX-Klar-Score-Spam: 0.98X-Klar-Model-Version: …
Klar decides. In shadow mode, Stalwart's own verdict is written into every message and never acted on.

What it needs

x86_64
Debian 12, Ubuntu 22.04 or later, with systemd.
arm64
Debian 13, Ubuntu 24.04 or later, with systemd.
Older systems
The container ghcr.io/klar-im/klar-milterd, for amd64 and arm64.
Memory
About 700 MB of RSS with the model loaded. One message at a time.
Speed
A median of 6 s per message on a 2-vCPU VPS, on the CPU alone. Stalwart waits 60 s, then delivers unclassified.
Stalwart
Version 0.16, with admin access through stalwart-cli.

Licences

The code is open source under AGPLv3. The model has its own licence, CC-BY-NC-4.0: free for non-commercial use, with attribution. The installer fetches it only once KLAR_ACCEPT_MODEL_LICENSE=1 says you have read that.

Non-commercial means a personal or family home server, a hobby project or academic research. Filtering mail for a company (its own staff included, a trial too) or a paid service needs a commercial licence, and that is Klar Pro. When in doubt, ask us at hello@klar.im before you deploy.

Questions operators ask

Does any of my mail leave the server?

No. The model runs next to the milter, on the same box, and classifying a message makes no network call. The only downloads happen at install: the release from GitHub, then the model.

What happens if the milter is down?

Stalwart delivers the message unclassified, with no X-Klar headers. The milter is set to fail open, so an outage never defers or bounces mail. Postfix behaves the same way with our config.

How do I upgrade?

Run the install command again. It keeps your config and skips model files that already match. To pin a release, add KLAR_VERSION=v0.2.0 after sudo, next to KLAR_ACCEPT_MODEL_LICENSE=1.

How do I uninstall it?

Undo the Stalwart side first. Run sieve_activate.py --deactivate for each mailbox. Delete the Klar milter and the global Sieve script klar, then turn the spam filter back on. If you used shadow mode, delete the KLAR_SHADOW tag and rule too. Set dmarcVerify back if you want the old policy. Then stop and disable klar-milterd and delete its unit file. Last, remove /opt/klar, /etc/klar, /var/lib/klar and the klarmilter user.

Can I run it in Docker?

Yes. The image ghcr.io/klar-im/klar-milterd runs on amd64 and arm64 and fetches the same model on first start. Bind its ports to 127.0.0.1, or put it on Stalwart's Docker network and pass --milter-host klar-milterd. The image has no apply.py: run the scripts from a clone of the engine repo. Never publish port 8891 to the internet.

Running mail for a company?

Klar Pro is the commercial licence for the same engine, on your own servers. Tell us about your setup.