How to read a spam score report for a WordPress site

A spam scorer takes a message and hands back two things: a number (“3.5/5.0, probably fine”) and a wall of rule names like MISSING_DATE, DKIM_INVALID, HTML_IMAGE_ONLY_04, with a dozen more below them. The number is the part everyone looks at. The rule list is the part that actually tells you what to fix.

Reading the number alone is where most WordPress operators lose the plot. A 6.8 provokes a rewrite from scratch of a template that only needed a Message-ID header; a 3.5 looks fine and gets ignored while the DKIM signature is quietly broken, the sending IP is on a dynamic range, and every password reset lands in Gmail’s spam folder anyway. Both failures come from the same move: treating the headline score as the diagnostic, when the score is a lagging summary of the rules below it.

The rest of this piece walks a real report in that order. The companion artefact is the nanoPost spam scorer, which runs the SpamAssassin pipeline against a pasted message and renders the rule list as a table.

What a spam score actually tells you

A scorer returns three artefacts, not one.

The score is a single number, calibrated per-tool: a 5.0 is SpamAssassin’s default spam threshold, a 7/10 is mail-tester’s recommended minimum passing grade, Rspamd’s reject action fires around 15 by default. The scales do not interchange, and none of them map to Gmail’s placement decision. The score is useful for triage (“is this message a disaster or a near-miss?”) and almost nothing else.

The rule list is where the diagnostic lives. Each row is a rule name, a per-rule score, and (if the tool bothers to render it) a plain-English translation. The per-rule scores sum to the headline. A handful of rules usually dominate the total; the rest are either noise or symptoms of the same underlying cause. A message sent unauthenticated from a shared relay routinely trips SPF_FAIL, DKIM_INVALID, and DMARC_FAIL at once: three rules, one fix.

The plain-text report block is the raw output the rule engine produced before the tool prettied it up. It includes rule hits with no scoring contribution (the ones the scorer could not price), the message headers the engine saw, and the test results verbatim. Useful when the per-rule table has rendered a rule you cannot interpret, or when a header-based rule is firing and you need to see the actual header the engine parsed.

The rest of this piece is organised around the rule list. The score is a header for the artefact, not the artefact itself.

The three scorers and what each measures

Three tools account for almost every spam report a WordPress operator will see. They look superficially similar, and they measure different things.

SpamAssassin

The oldest and most widely deployed scorer; the engine behind the nanoPost spam scorer, Postmark’s public spamcheck API, cPanel/WHM’s Apache SpamAssassin plugin, and a common content filter on self-hosted MTA stacks (bare Postfix + Amavis, Mail-in-a-Box). Rule-based: a large default rule set shipped by Apache (several hundred rules), each with a positive or negative score, plus any site-specific rules the operator has added. The default pass threshold is required_score 5.0; messages at or above the threshold are marked spam, and many deployments tune the threshold looser or stricter. Rule names are stable identifiers (SPF_FAIL, DKIM_INVALID, HTML_IMAGE_RATIO_02_06, BAYES_99), and reading them is the operator-facing skill the rest of this piece builds. Primary reference: spamassassin.apache.org.

Rspamd

The engine behind Mailcow, Mailu, and the Rspamd-first wing of self-hosted email appliances. (SpamAssassin remains in use on Mail-in-a-Box and the common bare Postfix + Amavis setups.) Rule-based and bayesian and fuzzy-hash and statistical; the report shape is different in a way that trips operators coming from SpamAssassin. Rspamd emits symbols (equivalent to SA rules) grouped by action: reject, add header, greylist, no action, with a composite score and a reason per symbol. Operators who run their own MTA see Rspamd’s verdict in the daemon’s log, in the JSON report that rspamc prints by default, and (when the milter headers module is configured) in X-Spamd-Result or X-Spam-Status headers on outgoing mail. The headers are not added by default; the module has to be enabled. The symbol taxonomy overlaps SpamAssassin’s for the authentication rules (both emit something recognisable as “SPF failed”) and diverges sharply for content heuristics: Rspamd’s FUZZY_DENIED (a hash-match against known-spam message bodies) and MULTIPLE_UNIQUE_HEADERS are symbols a SpamAssassin reader will not recognise. Primary reference: rspamd.com.

mail-tester

The web service most WordPress operators reach for first because it has the friendliest front end. mail-tester bundles SpamAssassin with its own authentication checks, DNSBL lookups across a wider list than SpamAssassin uses, and some content-level checks of its own; the output is scored on a 0 to 10 scale that compresses SpamAssassin’s negative-to-positive score into a user-facing grade. A mail-tester score of 7/10 is their recommended minimum pass; a mail-tester “7/10” is not a 7.0 SpamAssassin score. Underneath the grade is a drop-down list of findings, each one annotated and linked to a one-paragraph explanation. Primary reference: mail-tester.com.

The practical rule: if the tool is reading wp_mail() output from a WordPress site, SpamAssassin is doing the heavy lifting (either directly or inside mail-tester). Rspamd shows up when the operator runs their own MTA.

Walking through a real spam-scorer report

A WordPress order-confirmation email, sent from a misconfigured relay: an invalid DKIM-Signature: header, no Date:, no Message-ID:, a Received: chain with a documentation-range IP, and an HTML body with four product images and a single line of body copy. Scored on 2026-10-13 against the nanoPost spam scorer, which proxies Postmark’s public spamcheck (SpamAssassin 3.4.0 with Postmark’s rule set). The full test message and the raw response are in the show-your-work block at the bottom.

Score header

Score: 3.5 / 5.0 threshold
Verdict: below spam threshold

The headline score is 3.5. Under default SpamAssassin, the message would pass the content filter. That is the first useful lesson of the report: a message can carry four real deliverability problems and still score below threshold, because SpamAssassin’s thresholds are calibrated for spam detection, not for “will this reach the inbox at Gmail.” A sender who stops at the score ships a broken template.

Per-rule table

The scorer renders the rule list as a four-column table: rule name, score contribution, plain-English translation (from the tool’s rule-to-English map), and a fix link.

Rule Score What it means Fix
MISSING_DATE 1.4 No Date: header. WordPress’s wp_mail() normally supplies one via PHPMailer; if it is missing, the message was probably assembled by hand or by a broken plugin. troubleshoot WordPress email
RCVD_ILLEGAL_IP 1.3 A Received: header contains an IP in a reserved / documentation / private range (like 192.0.2.x or 10.x.x.x). Fine for a pasted example; a real deliverability red flag in production. troubleshoot WordPress email
HTML_IMAGE_ONLY_04 0.3 The message is mostly image, with almost no text. Gmail clipping analyzer
DKIM_SIGNED 0.1 The message carries a DKIM signature (informational: this rule does not confirm the signature is valid, only that one exists). DKIM inventory
DKIM_INVALID 0.1 A DKIM signature is present but does not verify. Common causes: the message was modified in transit, the wrong selector, or the private key does not match the published public key. DKIM inventory
MIME_HTML_ONLY 0.1 The message is HTML-only, with no text/plain part. Send a multipart/alternative with both: the plain text half is what accessibility tools and old clients read. Gmail clipping analyzer
MISSING_MID 0.1 No Message-ID: header. Every legitimate sender stamps one; its absence is a strong spam signal and breaks conversation threading. troubleshoot WordPress email
HTML_MESSAGE 0.0 The message contains HTML. Scored at zero on its own; informational. reference

Two rules carry 77% of the score (MISSING_DATE and RCVD_ILLEGAL_IP), one more carries another 9% (HTML_IMAGE_ONLY_04), and the rest sum to the remainder. The characteristic shape of a report in production is this exact distribution: a short head of rules with real score contributions and a long tail of 0.0 to 0.1 hits.

The two rules in the head are also the two the reader should look at first, in that order: fix the missing header, fix the sending-relay misconfiguration that put a documentation-range IP in the Received: chain, and the score drops below 1.0.

Full report block

Below the table, the scorer renders the raw plain-text output the SpamAssassin engine produced:

 pts rule                        description
---- --------------------------- -----------------------------------------------
 0.0 URIBL_BLOCKED               ADMINISTRATOR NOTICE: The query to URIBL
                                 was blocked [URIs: example-shop.com]
 1.3 RCVD_ILLEGAL_IP             Received: contains illegal IP address
 0.0 RCVD_IN_ZEN_BLOCKED_OPENDNS RBL: query to zen.spamhaus.org was blocked
                                 due to usage of an open resolver
                                 [203.0.113.42 listed in zen.spamhaus.org]
 0.0 URIBL_DBL_BLOCKED_OPENDNS   query to dbl.spamhaus.org was blocked due
                                 to usage of an open resolver
 0.0 URIBL_ZEN_BLOCKED_OPENDNS   query to zen.spamhaus.org was blocked due
                                 to usage of an open resolver
 0.1 MIME_HTML_ONLY              BODY: Message only has text/html MIME parts
 0.3 HTML_IMAGE_ONLY_04          BODY: HTML: images with 0-400 bytes of words
 0.0 HTML_MESSAGE                BODY: HTML included in message
 0.1 DKIM_SIGNED                 Message has a DKIM or DK signature, not
                                 necessarily valid
 0.1 MISSING_MID                 Missing Message-Id: header
 1.4 MISSING_DATE                Missing Date: header
 0.1 DKIM_INVALID                DKIM or DK signature exists, but is not valid

The report block is where you look when the per-rule table is missing something you want to see. In this case the table has eight rows and the raw block has twelve: the extra four are the blocklist query rules (URIBL_BLOCKED and the three *_BLOCKED_OPENDNS entries) that always fire at zero cost because Postmark’s public spamcheck environment cannot query Spamhaus or URIBL from an open resolver. Learning to skip them on sight saves time on every subsequent report.

Note what the message was not flagged for. There is no SPF_FAIL or DMARC_FAIL in the report; the response carried no SPF or DMARC verdicts, consistent with Postmark’s public spamcheck returning content-level findings rather than running live DNS lookups against the sender. The scorer is a content filter with authentication fingerprinting bolted on, not a full receiver simulation. For real SPF/DMARC verdicts on a live message, Gmail’s own authentication results header (Authentication-Results: on a message sent to a Gmail inbox) is the authoritative source; the DNS auth checker is the lookup side.

Reading SpamAssassin rule names at a glance

The reader does not need to memorise every rule. They need to recognise categories by name prefix. SpamAssassin’s naming convention, and the categorisation the spam scorer’s rule map inherits from it, has six buckets:

Bucket Name prefixes Example rules What it signals
auth SPF_*, DKIM_*, DMARC_* SPF_FAIL, DKIM_INVALID, DMARC_FAIL, SPF_SOFTFAIL, DKIM_ADSP_ALL Authentication outcomes. Almost always means “fix your DNS or your signing”.
headers MISSING_*, NO_*, some MIME_* MISSING_DATE, MISSING_MID, MISSING_SUBJECT, NO_RELAYS Missing or malformed headers. Usually one-line fixes in whatever is generating the message.
envelope FROM_*, RCVD_*, HELO_*, HEADER_FROM_DIFFERENT_DOMAINS FROM_ADDR_WS, HELO_DYNAMIC_IPADDR, NO_DNS_FOR_FROM, RCVD_ILLEGAL_IP Addressing and relay layer. Usually indicates the sending infrastructure is wrong, not the message.
content HTML_*, BODY_*, MIME_*, BAYES_*, TVD_* HTML_IMAGE_RATIO_02, BODY_URI_ONLY, MIME_HTML_ONLY, BAYES_* family Body heuristics. The most subjective bucket and the easiest one to over-fix.
rbl RCVD_IN_*, URIBL_* RCVD_IN_ZEN (sending IP on Spamhaus ZEN), URIBL_DBL_SPAM (URL in body on Spamhaus DBL) DNS blocklist hits. Reputation signals; the fix is almost never in the message.
postmark *_BLOCKED_OPENDNS, URIBL_BLOCKED, POSTMARK_* RCVD_IN_ZEN_BLOCKED_OPENDNS, URIBL_DBL_BLOCKED_OPENDNS Fingerprints of the Postmark public spamcheck environment, not verdicts on your mail. Safe to ignore.

Two rule-name quirks are worth absorbing. First, BAYES_* is the one bucket where the rule’s own number carries information: BAYES_99 means the trained classifier is 99% confident the message is spam; BAYES_00 means 0%. The score contribution matters less than the raw probability the number encodes. Second, HTML_IMAGE_RATIO_02_06 and similar compound names encode score thresholds in the name itself; the _02_06 suffix means “ratio between 0.2 and 0.6”. You do not need to remember the thresholds, only that the rule fired because an image-heavy body crossed one.

A reader who has not seen a particular rule before can usually place it from the prefix alone. That is the whole point of the convention; it rewards the pattern-match over the lookup.

Fix order: auth first, headers second, content last

A rule list tempts the reader to fix everything at the top. In practice a handful of rules carry most of the score and the rest resolve or shrink when the top ones are fixed. The fix order below is explicit, and the order matters.

Auth rules first. A message that fails SPF (Sender Policy Framework) is a DNS TXT record that lists the servers allowed to send mail using your domain. Receivers compare the sending IP to that list and use the result as one input to their accept/reject decision. Read full reference →, DKIM (the cryptographic signature receiving servers use to confirm a message came from your domain), or DMARC (Domain-based Message Authentication, Reporting, and Conformance) is a DNS TXT record that tells receivers what to do with mail from your domain that fails SPF or DKIM, and where to send reports about those failures. Read full reference → alignment usually trips multiple rules at once, and one DNS or signing fix cascades into fixing all of them. The order matters for a second reason too: major mailbox providers reject or quarantine messages that fail authentication, often before the content filter runs. The DNS setup guide covers the records themselves; the DNS auth checker confirms they resolve the way the sending IP expects.

Header rules second. Missing Date:, missing Message-ID:, malformed Content-Type: are almost always one-line fixes in whatever is generating the message, and almost always indicate a misconfigured sender rather than a problem with the message content. A WordPress message missing Message-ID usually means a plugin is bypassing wp_mail() and calling PHP’s mail() function directly, and the fix is upstream of the template. The WordPress email troubleshooting guide walks the common ones.

Content rules last. HTML_IMAGE_RATIO_*, BODY_URI_ONLY, MIME_HTML_ONLY, and the whole BAYES_* family sit at the bottom because they are the most subjective, the easiest to over-fix, and the most likely to be noise once the auth and header rules are clean. An HTML_IMAGE_ONLY_04 hit on a transactional email with a logo and a short covering sentence is a textbook false positive; chasing it leads to re-templating work that does not change the inbox outcome.

The meta-rule, applied after a round of fixes: if the headline score drops by more than two points after the top contributors are fixed, the remaining low-scoring rules are not worth individual attention. Re-score, confirm the number is below threshold, and move on.

What a spam score does not tell you

Three things a scorer cannot answer, and where to look instead.

  • Gmail, Outlook, and Yahoo placement. Their classifiers are not SpamAssassin and do not share rule sets. A 1.0 SpamAssassin score is not an inbox guarantee at Gmail, and a 6.0 is not a hard spam-folder sentence. For a real placement answer, work through how to test WordPress email inbox placement.
  • Sender reputation. A message that passes authentication and scores 0.0 can still land in spam because the sending IP’s reputation is poor at that specific receiver. The scorer sees the message, not the IP’s history. Gmail Postmaster Tools and Microsoft SNDS are the reputation feeds; the bulk sender authentication checklist covers the reputation thresholds the 2024 Gmail/Yahoo rules put in place.
  • Trends across time. The scorer is a per-message diagnostic. For trend data, DMARC aggregate reports are the matching artefact: structured XML from every receiver that saw mail from the domain, aggregated daily.

The scorer is a tight loop: paste the message, read the rules, fix the top contributors, re-score. For the three things above, other instruments exist and this publication’s back catalogue covers them in depth.

Show your work

Test message. A WordPress order-confirmation email, assembled to trip rules across three categories (headers, envelope, content). The raw .eml payload:

Received: from relay.example-wp-host.net (relay.example-wp-host.net
    [203.0.113.42]) by mx.gmail.com with ESMTP id abc123;
    Tue, 13 Oct 2026 09:00:00 +0000
From: Your Shop <[email protected]>
To: Alice Customer <[email protected]>
Reply-To: [email protected]
Subject: Your order #1248 is confirmed - 50% free shipping included
MIME-Version: 1.0
Content-Type: text/html; charset=UTF-8
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
    d=example-shop.com; s=mail; h=from:to:subject:date;
    bh=invalid; b=NOTAREALSIGNATURE

<html><body><table width=600><tr><td>
<img src=https://cdn.example-shop.com/logo.png></td></tr><tr><td>
<img src=https://cdn.example-shop.com/p1.jpg>
<img src=https://cdn.example-shop.com/p2.jpg>
<img src=https://cdn.example-shop.com/p3.jpg>
<img src=https://cdn.example-shop.com/p4.jpg></td></tr>
<tr><td><p>Thanks!</p></td></tr></table></body></html>

Where the test ran. Postmark’s public spamcheck endpoint, https://spamcheck.postmarkapp.com/filter, which the nanoPost spam scorer proxies. SpamAssassin 3.4.0 (the version Postmark publishes for its inbound filtering stack) with Postmark’s rule set and default score set.

When. 2026-10-13. Postmark’s rule set and score weights do not change between releases often, but a reader reproducing the test months later may see small variations in individual rule scores if Postmark has updated the ruleset in the interim; the shape of the report (which rules fire for a message of this kind) is stable across those updates.

What this walkthrough does not show. The response carried no SPF_FAIL, SPF_PASS, or DMARC_* verdicts, consistent with the public spamcheck returning content-level findings rather than running live DNS lookups against the sender. Those receiver-side verdicts live in the Authentication-Results: header on a message delivered to a real Gmail (or other receiver) inbox. The scorer is a content filter plus authentication fingerprinting, not a receiver.