A spam filter for Node.js, the command line and mail servers. It catches spam, phishing, scams and malware in any language, explains every decision, and can ask a language model about close calls.
spamscanner.net · Getting started · Documentation
Features
- A classifier that reads every language. Words are segmented by Unicode rules, so Chinese, Japanese and Thai work like English. Lookalike letters, invisible characters,
v1agraspellings and styled letters are undone before counting. Links, senders, HTML and attachments count too, not only words. - Careful with languages it has not learned. A language the model saw little ham in gets "unsure" rather than "spam", and the other checks decide.
- Phishing checks. Lookalike domains (
pаypal.comwith a Cyrillic а,paypa1.com, punycode), links whose text shows another address, brand names in display names, and Cloudflare's malware and adult-content resolvers. - Attachment checks. Executables found by their bytes, also when renamed to
.pdf; double extensions; right-to-left filename tricks; executables in ZIP files; Office macros; active PDFs; and ClamAV. - SPF, DKIM, DMARC and ARC, DNS blocklists, allow and deny lists.
- Language models for close calls. Ollama, LM Studio, llama.cpp, vLLM, Claude, ChatGPT, Gemini, Mistral, Groq, OpenRouter and any OpenAI-compatible server, on any URL, port and authentication, plus the decision models Cloudflare Clef and TypeSafe Jev. By default the model returns a probability for each verdict in one step instead of writing an answer. Personal data is removed before mail goes to a remote provider.
- Every way to plug in. A Node.js library, a command line, a Postfix and Sendmail milter, a Postfix content filter, a SpamAssassin-compatible spamd server for spamc, Exim and Haraka, an HTTP API and a TCP server.
X-Spam- headers.
- Trainable. Train on your own mbox files, Maildirs or datasets, measure the result, and learn from "report spam" buttons.
Install
npm install --global spamscanner # the command line
npm install spamscanner # the library
Or a standalone binary, with nothing else to install:
curl -fsSL https://github.com/spamscanner/spamscanner/releases/latest/download/install.sh | bash
The package needs Node.js 18 or later.
Use
spamscanner scan message.eml
SPAM score 16.3 (spam at 5.0, reject at 15.0) action: reject language: en
+6.3 BAYES_999 Classifier spam probability 99.9%
+5.0 PHISHING_LOOKALIKE_DOMAIN "paypa1-secure.top" imitates paypal by swapping characters
+3.0 DECEPTIVE_LINK A link shows "https://www.paypal.com/signin" but goes to paypa1-secure.top
+2.0 FROM_NAME_BRAND Display name says "paypal" but the message is from paypa1-secure.top
import SpamScanner from 'spamscanner';
const scanner = new SpamScanner();
const result = await scanner.scan(rawMessage, {
session: {remoteAddress: '203.0.113.5', envelope: {rcptTo: [{address: '[email protected]'}]}},
});
result.isSpam; // true
result.action; // 'accept', 'tag' or 'reject'
result.tests; // [{name: 'DECEPTIVE_LINK', score: 3, description: '...'}, ...]
With Postfix:
spamscanner milter --port 7831 --auth --subject-tag "[SPAM]"
sudo postconf -e smtpd_milters=inet:127.0.0.1:7831 milter_default_action=accept
sudo postfix reload
With a local language model:
ollama pull qwen3.5:4b
spamscanner scan message.eml --llm ollama --llm-model qwen3.5:4b
Documentation
- Getting started
- Command line
- Postfix and Sendmail, other mail servers, HTTP API and spamd
- How it works, tests and scores, languages
- Language models, with recommended open models from Hugging Face
- Training
- Forward Email, and upgrading from version 5 or 6
- API reference
- Security and privacy
Development
npm install
npm test # lint, build, and every test, with 100% coverage required
npm run test:e2e # end-to-end tests against Postfix, ClamAV, spamc and Ollama; see test/e2e/
npm run model:train # rebuild the bundled model from public datasets
npm run site:build # build spamscanner.net into _site/
npm run site:serve # preview it at http://localhost:8080
npm run i18n:check # check the translations against the English pages
