Reading a SpamAssassin Report Rule by Rule
Turn a spam score into a fix list: rule categories, the hits worth acting on, negative-score rules, and why the largest number is usually not the one to chase.
A spam report is a list of rule names and point values, and the natural reaction — fix the biggest number — is usually wrong. This guide covers how to read the report by category, which hits are worth acting on, and why a template can be improved more by removing three small structural hits than by chasing one large one.
Root Cause: The Score Is a Sum of Unrelated Judgements
The total is not a measurement of one thing. It is the arithmetic sum of every rule that matched, and those rules test entirely different properties: the shape of the MIME structure, the wording of the subject, the reputation of a link's domain, whether a header is present. Two messages with the same score can have nothing in common.
That matters because the rules differ in how actionable they are. A structural hit describes something your build pipeline produced and can stop producing. A network hit describes the current state of a third party. A meta rule describes a combination of other rules and has no independent cause at all. Treating them as interchangeable point values — and attacking the largest — leads to time spent on the hits you cannot change.
Reports also include negative rules, which subtract points for signals of legitimacy: a valid DKIM signature, a plausible date header, a well-formed message ID. A message with a raw positive score of 4.0 and negative rules totalling −2.5 has a net of 1.5 and is perfectly healthy. Reading only the positive rules overstates the problem.
Reading the Report
A report line has three parts: the points, the rule name, and a description. The rule name is the part that carries information, because it is stable across versions and searchable.
pts rule name description
---- ---------------------- --------------------------------------------------
1.1 MIME_HTML_ONLY Message only has text/html MIME parts
0.8 HTML_IMAGE_RATIO_02 HTML has a low ratio of text to image area
0.5 URI_NOVOWEL URI hostname has long non-vowel sequence
0.4 HTML_MESSAGE HTML included in message
-0.1 DKIM_VALID Message has at least one valid DKIM signature
-0.1 DKIM_VALID_AU Message has a valid DKIM signature from author domain
-1.0 SPF_PASS SPF check passed
Three observations from that sample, all typical.
HTML_MESSAGE at 0.4 is unavoidable — it fires on every HTML message ever sent, and there is no version of your template that does not trip it. Rules like this form a baseline that every legitimate sender carries.
MIME_HTML_ONLY at 1.1 is the largest actionable hit, and it is a build-pipeline defect: the message has no plain-text part. One change to how the message is composed removes it permanently.
URI_NOVOWEL at 0.5 is a link-structure hit, usually caused by a tracking URL with a long random token in the hostname rather than the path. Moving the token into the path, or using a CNAME'd tracking subdomain, removes it.
What to Fix, in Order
| Category | Example rules | Action | Recurs? |
|---|---|---|---|
| Structural | MIME_HTML_ONLY, MISSING_DATE, MISSING_MID |
Fix in the composer | No — fixed permanently |
| Content | SUBJ_ALL_CAPS, HTML_IMAGE_RATIO_* |
Rewrite or rebalance | Only if reintroduced |
| Link | URI_NOVOWEL, URI_SHORTENER |
Change link construction | No |
| Baseline | HTML_MESSAGE |
Nothing — unavoidable | Always present |
| Network | RCVD_IN_*, URI blocklists |
Not a build concern | Varies daily |
| Meta | *_MULTI, combination rules |
Fix the constituents | Clears itself |
The ordering is by return on effort rather than by points. Structural hits are one-time fixes in code that never recur; content hits need a judgement call each time; network hits are outside the template entirely. A team that works the table top-down usually finds the score drops below the gate before reaching the third row.
Variant: Rules You Should Deliberately Ignore
Some hits are correct and should stay. A transactional receipt genuinely contains a currency amount, an order number and a call to action; rules that treat those as commercial signals are describing the message accurately. Rewriting a receipt to avoid them makes it worse for the reader and moves the score by a fraction of a point.
The test is whether the change improves the message for its recipient. Adding a plain-text part improves it. Removing the price from a receipt does not. Where a rule and the reader disagree, the reader wins — and the remaining points are absorbed by having a clean structural baseline everywhere else.
Turning a Report Into a Ticket
The report is read once and acted on over days, so the useful output is not the score but a short list of specific changes with owners. Three questions convert one into the other.
Who owns the fix? Structural hits belong to whoever maintains the message composer, and they are fixed once for every template. Content hits belong to whoever writes the copy, and they are judgement calls. Link hits usually belong to whoever configured the ESP's tracking, and they are a configuration change rather than a code one. A report handed wholesale to the template author sends two of those three to the wrong person.
Is the fix template-specific or systemic? A missing text part is systemic — if one template lacks it, they all probably do, and fixing the composer resolves the whole set at once. An all-capitals subject is template-specific. Distinguishing them prevents the same rule being fixed eleven times, once per template, over the following month.
What is the expected point reduction? Estimating this before the work is what stops a team spending an afternoon on a 0.2-point content rule while a 1.1-point structural hit sits untouched. The report gives you the number directly, so the prioritisation requires no analysis beyond sorting.
A worked example makes the shape clear. A report totalling 4.2 against a 3.0 gate, with MIME_HTML_ONLY +1.1, HTML_IMAGE_RATIO_02 +0.8, URI_NOVOWEL +0.5, HTML_MESSAGE +0.4 and network rules making up the rest, produces exactly three tickets: add a text part to the composer (systemic, one change, −1.1), rebalance the template's image-to-text ratio (template-specific, −0.8), and move the tracking token out of the hostname (configuration, −0.5). The baseline rule and the network rules produce no ticket at all. Two of the three land the message under the gate on their own.
Pipeline Integration
Print the rule list on failure, sorted by points, with the category annotated. A CI failure that says "score 4.2, gate 3.0" sends the developer to search for the rule names; one that lists MIME_HTML_ONLY +1.1 (structural — add the text part) contains the fix.
Pin the rule set version in the CI image. SpamAssassin's rules are updated, and an unpinned image means a score can change without your template changing — which produces a failure nobody can attribute and, after two or three occurrences, a gate people stop trusting. Keep the network rules disabled in this deterministic check and let the seed panel cover the live signals.
Validation and Deployment Checklist
Frequently Asked Questions
Why does the score differ between two runs of the same message?
Almost always a network rule — a blocklist lookup resolving differently, or a URI reputation check returning a fresh answer. Disable those rules in the deterministic check and the score becomes reproducible. If it still varies, the message itself is not identical between runs, which usually means live data is leaking into what should be fixture content.
Can we just raise the gate threshold?
You can, and it is occasionally right — a gate that fires constantly on unavoidable baseline rules is miscalibrated. But raising it should follow an analysis showing the remaining points are genuinely unavoidable, not a reaction to a failing build. The usual outcome of that analysis is one structural fix that brings the score back under the original gate.
Do negative rules mean we should chase DKIM for the points?
No — you should have valid authentication for its own sake, and the point credit is a side effect. The credit is small compared with what authentication does for placement at the major providers, where a DMARC alignment failure matters far more than a fraction of a SpamAssassin point.
Is a score of zero achievable?
Not for an HTML message. HTML_MESSAGE alone puts a floor under it, and several other baseline rules fire on any well-formed commercial message. A net score in the low single digits with no structural hits is the practical target, and chasing lower has no effect on placement at the providers that matter.
Related
- Spam Filter and Inbox Placement Testing — where this check sits in the pipeline
- Reducing Spam Score in Image-Heavy Emails — the most common content hit in detail
- Setting Up a Seed-List Inbox Placement Test — the live half the network rules belong to
- Email Authentication: SPF, DKIM, and DMARC — the negative rules' underlying subject
← Back to Spam Filter and Inbox Placement Testing