TacitusMail
All posts
Infrastructure

Pro-grade anti-spam: Rspamd, Spamhaus and a local recursive resolver

Every inbound message now runs through a full Rspamd + postscreen stack with Spamhaus, SURBL, URIBL, SpamCop and Barracuda scoring — via our own sidecar Unbound so we stay inside the per-resolver free quota.

Tacitus Mail was accepting anything that spoke valid SMTP. As of today, every inbound message hits a proper filter stack before it reaches the mailbox.

The pipeline

  1. Postscreen rejects known botnets at the TCP handshake. Weighted DNSBL sites: zen.spamhaus.org×3, bl.spamcop.net×2, b.barracudacentral.org×2, dnsbl.sorbs.net×1, threshold 3. Anything that hits threshold never spawns an smtpd worker.
  2. Rspamd as a milter on :11332, scoring against Spamhaus DBL, SURBL, URIBL, fuzzy hashes from fuzzy.rspamd.com, SPF/DKIM/DMARC alignment, a Bayesian classifier (redis-backed, autolearning) and a neural network model. Reject ≥ 15, rewrite Subject ≥ 10, X-Spam header ≥ 6, greylist ≥ 4.
  3. OpenDKIM chains after Rspamd so outbound mail still gets signed with our RSA-2048 selector.

Why Unbound

Spamhaus rate-limits shared public resolvers (1.1.1.1, 8.8.8.8, your ISP), because once the resolver crosses ~100 k queries/day against them everyone using it starts getting NXDOMAIN back. We added a dedicated recursive resolver (a 15 MB Alpine image running Unbound) that resolves DNSBL zones directly from the root servers. From Spamhaus's point of view, we are now our own resolver and we have our own full per-IP quota.

Testing it

We fire the GTUBE test string through postfix:25:

XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X

Rspamd scores it 15.00 from the GTUBE rule and the milter returns 554 5.7.1 Gtube pattern at DATA. Done.

Check your Rspamd dashboard at /rspamd/ (admin only) to watch scores in real time, retrain the Bayesian classifier on false positives, and see which DNSBLs are firing most.

T
The Tacitus Mail team
Engineering posts and release notes from the people who write the code.

More from the blog