Self-hosted spam filter

Spam Scanner runs on your own server and keeps the mail there. The classifier model ships inside the package, there is no telemetry or update check, and by default the only thing it sends is a DNS lookup for the host name of each link.

What leaves the server#

By default, only the host names of links in a message. They are looked up on Cloudflare's filtering resolvers (1.1.1.2 and 1.1.1.3) as ordinary DNS queries, and no part of the message or its addresses is sent. --no-cloudflare turns this off.

Every other check that uses the network is off until you turn it on:

CheckTurn it on withWhat it sends
SPF, DKIM, DMARC, ARC--authDNS queries for the sender's records
DNS blocklists--dnsbl, --uriblThe client's IP address and link domains, as DNS queries
ClamAV--clamavAttachments, to your own clamd
A language model--llmA summary of the message, to the server you name

Scans are not logged or stored. Security and privacy

Install on a server#

Shell
npm install --global spamscanner

Or the standalone binary, with Node.js and the model built in:

Shell
curl -fsSL https://github.com/spamscanner/spamscanner/releases/latest/download/install.sh | bash

The install script checks the binary against the release's SHA256SUMS file before installing it, and each binary has a signed build provenance attestation that gh attestation verify checks. Binaries cover Linux (x64 and arm64), macOS and Windows. The Linux binaries need glibc, so on Alpine install with npm. Getting started

There is no update check. To update, run the install command again: both commands install the latest release.

Run it as a service#

The milterMail filter: a protocol from Sendmail, also used by Postfix, that lets a separate program inspect each message during the SMTP session., HTTP, TCP and spamdSpamAssassin's daemon. Its protocol is spoken by spamc, Exim, Haraka and other SpamAssassin clients. servers listen on 127.0.0.1 unless --host says otherwise. Run them as an unprivileged user, for example under systemd:

INI
[Service]
ExecStart=/usr/local/bin/spamscanner milter --port 7831 --auth --subject-tag [SPAM]
User=spamscanner
Group=spamscanner
Restart=on-failure
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes

The full unit and its setup are in the Postfix guide. None of the servers speak TLS, so to reach one from another machine, use a private network, an SSH tunnel or a reverse proxy with TLS.

Connect your mail server#

Mail serverCommandPort
Postfix or Sendmailspamscanner milter7831
Postfix, never refusing mailspamscanner filter, a content filternone
Exim, Haraka or spamcspamscanner spamd783
Anything that speaks HTTPspamscanner http7832
A plain TCP clientspamscanner server7830
A mail server written in Node.jsimport SpamScanner from 'spamscanner'none

The servers can add X-Spam-Flag, X-Spam-Score and X-Spam-Status headers to the message, and the library returns the score and every test that fired. Guides: Postfix spam filter, other mail servers and the HTTP API.

Keep language models local#

A language model is optional and only reads close calls: clear spam and clear hamWanted mail: the opposite of spam. never reach it. With OllamaA program that downloads and runs open language models on your own machine, with an HTTP API. it runs on the same server, so no message leaves it:

Shell
ollama pull qwen3.5:4b
spamscanner milter --llm ollama --llm-model qwen3.5:4b

qwen3.5:4b runs on a CPU. With a hosted provider instead, personal data is removed from a message before it is sent. Language models

Train it on your own mail#

The bundled model is trained on public datasets. A model trained on the mail your server receives does better, and it stays on that server:

Shell
spamscanner train --spam ~/Maildir/.Junk --ham ~/Maildir/cur --out /var/lib/spamscanner/model.json
SPAMSCANNER_MODEL=/var/lib/spamscanner/model.json spamscanner milter

A model file holds hashed feature counts, not words or message text. Training

Requirements#

Node.js 18 or later on Linux, macOS or Windows, or nothing at all with a standalone binary. ClamAVAn open-source antivirus engine. Its daemon, clamd, scans files sent over a socket. and Ollama are optional and run as their own services.

Edit this page on GitHub · Markdown ·