BSNSoft · Domain Reputation
Integration

Integrate the reputation service in your mail filter

Classic DNSBL at dnsbl.bsnsoft-hostings.de. HTTP API available as an alternative transport.

Rate limits & registration

Queries are rate-limited per source IP (the IP we see — not your mailserver, but the DNS resolver it uses):

TierLimitOver-limit behaviour
Free / unregistered20 queries/hour per IPNXDOMAIN (silent drop)
Registered (sign up free)100 queries/hour per registered IPAnswered, but flagged — dashboard shows a sales hint after first overage
Paid / EnterpriseHigher volumes & SLAOn request — contact details after signup
⚠ Important: open resolvers fall into the free tier!

If your mailserver uses a public resolver like 8.8.8.8, 1.1.1.1, or 9.9.9.9 as DNS forwarder, we see the resolver's IP — shared with thousands of other users. That traffic hits the 20/hour free-tier limit collectively, and you get silently dropped. Fix:

Classification

The service answers with one of five classifications, encoded in the DNS return code:

ScoreClassSymbolDNS returnRecommended weight
9TOPBSN_REP_TOP127.0.0.0−2.0 (bonus)
7–8HIGHBSN_REP_HIGH127.0.0.[1-2]−0.5 (small bonus)
4–6MEDIUMBSN_REP_MEDIUM127.0.0.[3-5]+1.0
2–3LOWBSN_REP_LOW127.0.0.[6-7]+4.0
0–1BADBSN_REP_BAD127.0.0.[8-9]+6.0

With an RSpamD reject threshold of 12, our maximum contribution (+6.0) stays below half – our signal alone never rejects a message; only in combination with another indicator (DMARC fail, Bayes, IP RBL, …).

RSpamD · Lua plugin (recommended)

Sender-domain lookup via our DNSBL. Small Lua file, loaded once, runs for every message. This is the cleanest integration for sender-domain reputation.

Step 1 – save as /etc/rspamd/local.d/bsn_reputation.lua:

local rspamd_logger = require "rspamd_logger"
local N = "bsn_reputation"
local RBL = "dnsbl.bsnsoft-hostings.de"

local function reputation_cb(task)
  local from = task:get_from("smtp")
  if not from or not from[1] then from = task:get_from("mime") end
  if not from or not from[1] then return false end
  local domain = (from[1]["domain"] or ""):lower()
  if domain == "" then return false end

  local resolver = task:get_resolver()
  if not resolver then return false end

  resolver:resolve_a({
    task = task,
    name = domain .. "." .. RBL,
    callback = function(_, _, results, err)
      if err or not results or #results == 0 then return end
      local last = tonumber(tostring(results[1]):match("%.(%d+)$"))
      if not last then return end
      local sym
      if     last == 0 then sym = "BSN_REP_TOP"
      elseif last <= 2 then sym = "BSN_REP_HIGH"
      elseif last <= 5 then sym = "BSN_REP_MEDIUM"
      elseif last <= 7 then sym = "BSN_REP_LOW"
      else                  sym = "BSN_REP_BAD"
      end
      task:insert_result(sym, 1.0, domain)
    end,
  })
  return false
end

local id = rspamd_config:register_symbol({
  name = "BSN_REPUTATION_CHECK", type = "callback", callback = reputation_cb,
})
for _, sub in ipairs({"BSN_REP_TOP","BSN_REP_HIGH","BSN_REP_MEDIUM","BSN_REP_LOW","BSN_REP_BAD"}) do
  rspamd_config:register_symbol({ name = sub, type = "virtual", parent = id, group = N })
end
rspamd_logger.infox(rspamd_config, "%s: loaded (zone=%s)", N, RBL)

Step 2 – activate it by adding this line to /etc/rspamd/rspamd.local.lua:

dofile("/etc/rspamd/local.d/bsn_reputation.lua")

Step 3 – set weights in /etc/rspamd/local.d/scores.conf:

BSN_REP_TOP    = -2.0;
BSN_REP_HIGH   = -0.5;
BSN_REP_MEDIUM =  1.0;
BSN_REP_LOW    =  4.0;
BSN_REP_BAD    =  6.0;

Step 4 – systemctl restart rspamd. Verify with:

printf "From: t@heise.de\nTo: r@x.de\nSubject: t\n\nbody\n" | \
  rspamc -F "t@heise.de" - | grep BSN_REP

RSpamD · rbl module (optional, for body-URLs)

The Lua plugin above covers sender-domain reputation. If you additionally want to match domains that appear as URLs inside the body text, add the native rbl module on top. Be careful not to set weights that double-count between sender-based (Lua) and URL-based (rbl) hits.

File /etc/rspamd/local.d/rbl.conf:

rbls {
  bsnsoft_reputation_urls {
    symbol = "BSN_URL_REPUTATION";
    rbl = "dnsbl.bsnsoft-hostings.de";
    checks = ["urls", "content_urls"];
    emails_domainonly = true;
    unknown = false;
    returncodes {
      BSN_URL_TOP    = "127.0.0.0";
      BSN_URL_HIGH   = "127.0.0.[1-2]";
      BSN_URL_MEDIUM = "127.0.0.[3-5]";
      BSN_URL_LOW    = "127.0.0.[6-7]";
      BSN_URL_BAD    = "127.0.0.[8-9]";
    }
  }
}

Separate symbol names (BSN_URL_*) prevent double-scoring with the Lua sender-check (BSN_REP_*). Typical weights: use smaller numbers for URL hits, since most legit mails contain URLs to mid-reputation domains (shorteners, trackers).

Weights in /etc/rspamd/local.d/scores.conf:

symbols {
  "BSN_REP_TOP"    { score = -2.0; description = "Top reputation"; }
  "BSN_REP_HIGH"   { score = -0.5; description = "Strong reputation"; }
  "BSN_REP_MEDIUM" { score =  1.0; description = "Neutral / unclear reputation"; }
  "BSN_REP_LOW"    { score =  4.0; description = "Weak reputation"; }
  "BSN_REP_BAD"    { score =  6.0; description = "Throwaway / spam infrastructure"; }
}

Reload after changes: systemctl reload rspamd.

SpamAssassin · AskDNS (Postfix + amavis)

The zone answers in graded form: the last octet is 9 − score, so 127.0.0.0 means TOP and 127.0.0.9 means BAD (see Classification above). Every answer is a valid A record, including the good ones. Two traps when you set this up in SpamAssassin:

Sender-domain lookup via the AskDNS plugin (SpamAssassin 3.4 or later). Make sure it is loaded: loadplugin Mail::SpamAssassin::Plugin::AskDNS in /etc/spamassassin/v340.pre (active by default on most distributions). _AUTHORDOMAIN_ is the domain of the From: header. Save as /etc/spamassassin/bsn_reputation.cf:

ifplugin Mail::SpamAssassin::Plugin::AskDNS

askdns   BSN_REP_TOP    _AUTHORDOMAIN_.dnsbl.bsnsoft-hostings.de A /^127\.0\.0\.0$/
describe BSN_REP_TOP    Sender domain: top reputation (BSNSoft)
tflags   BSN_REP_TOP    net nice
score    BSN_REP_TOP    -0.8

askdns   BSN_REP_HIGH   _AUTHORDOMAIN_.dnsbl.bsnsoft-hostings.de A /^127\.0\.0\.[12]$/
describe BSN_REP_HIGH   Sender domain: high reputation (BSNSoft)
tflags   BSN_REP_HIGH   net nice
score    BSN_REP_HIGH   -0.2

askdns   BSN_REP_MEDIUM _AUTHORDOMAIN_.dnsbl.bsnsoft-hostings.de A /^127\.0\.0\.[345]$/
describe BSN_REP_MEDIUM Sender domain: medium reputation (BSNSoft)
tflags   BSN_REP_MEDIUM net
score    BSN_REP_MEDIUM 0.4

askdns   BSN_REP_LOW    _AUTHORDOMAIN_.dnsbl.bsnsoft-hostings.de A /^127\.0\.0\.[67]$/
describe BSN_REP_LOW    Sender domain: low reputation (BSNSoft)
tflags   BSN_REP_LOW    net
score    BSN_REP_LOW    1.7

askdns   BSN_REP_BAD    _AUTHORDOMAIN_.dnsbl.bsnsoft-hostings.de A /^127\.0\.0\.[89]$/
describe BSN_REP_BAD    Sender domain: bad reputation (BSNSoft)
tflags   BSN_REP_BAD    net
score    BSN_REP_BAD    2.5

endif

The weights are the rspamd weights above, scaled to SpamAssassin's default threshold of 5 (rspamd: reject at 12). As with rspamd, our maximum contribution (+2.5) stays at half the threshold – our signal alone never marks a message as spam.

Optional: domains in body URIs

To also check domains that appear as links in the body, add a URIDNSBL rule. Match only the BAD range and keep the weight small, since most legit mails link to mid-reputation domains (shorteners, trackers):

ifplugin Mail::SpamAssassin::Plugin::URIDNSBL

urirhssub BSN_URI_BAD dnsbl.bsnsoft-hostings.de. A 127.0.0.8-127.0.0.9
body      BSN_URI_BAD eval:check_uridnsbl('BSN_URI_BAD')
describe  BSN_URI_BAD URI domain: bad reputation (BSNSoft)
tflags    BSN_URI_BAD net domains_only
score     BSN_URI_BAD 0.5

endif

Verify

spamassassin --lint
spamassassin -D askdns -t < mail.eml 2>&1 | grep -E 'BSN_|X-Spam-Status'

The debug output shows the query (launching query (BSN_REP_TOP): example.com.dnsbl.bsnsoft-hostings.de) and the answer for each rule; the report lists the rule that matched. With amavis, restart it afterwards (systemctl restart amavis) so it loads the new rules.

A domain we have not seen before returns NXDOMAIN on its first lookup and is rated in the background; later lookups get the score.

Postfix · postfwd policy

If RSpamD is not in use, a policy daemon such as postfwd can query the DNSBL:

id=BSN_REP_REJECT ; client_address!~=^127\. ; sender_domain_in_dnsbl==dnsbl.bsnsoft-hostings.de ; action=REJECT Poor sender reputation

HTTP API

Single lookup:

curl https://dnsbl.bsnsoft-hostings.de/api/score/example.com

Response format:

{
  "rawInput": "example.com",
  "normalizedDomain": "example.com",
  "score": 8,
  "classification": "HIGH",
  "status": "INDEXED"
}

Status values:

StatusMeaning
INDEXEDScore available in score / classification
INVALID_DOMAINInput is not a valid domain

Set generous client timeouts (at least 90 seconds).

Questions or enterprise usage?

Contact: info@bsnsoft.de · bsnsoft.de