Checking the spam score of a message
Every incoming message is run through SpamAssassin, so a test can assert that your mail lands under the threshold instead of finding out from an angry customer.
How it works
Delivery is not the same thing as arrival. A message can reach the inbox and still be filtered on the other side, and the reason is usually a missing signature or a rule you have never heard of. Each message carries a spam object with the score, the threshold it was judged against, and every rule that fired with its own weight — so a failing test can say which rule cost you the points. Nothing is blocked or hidden here: the score is information attached to a message that was delivered normally.
Assert the message is under the threshold
This is the assertion worth having in CI: your own mail should stay comfortably clean, and a regression in your SPF or DKIM setup shows up as a rising number.
msg = inbox.wait_for_message(timeout=30)
spam = msg.raw["spam"]
assert spam["is_spam"] is False
assert spam["score"] < 3.0
Find out which rule cost you the points
Every rule that fired comes back with its own weight, and negative weights are the ones working in your favour. SPF_PASS or DKIM_VALID pulling the score down is the sign your sending setup is doing its job.
for rule in sorted(msg.raw["spam"]["rules"], key=lambda r: -r["score"]):
print(rule["score"], rule["name"], rule["description"])
# 1.8 HTML_IMAGE_ONLY_28 BODY: HTML: images with 1600-2800 bytes of words
# -0.1 DKIM_VALID Message has at least one valid DKIM signature
When the field is null
A null spam object means the message was never scanned — not that it came back clean. Messages that arrived before scanning was switched on, or while the scanner was unreachable, look like this. A score of 0.0 is a real result; null is the absence of one.
curl -s "$MAILFLAT_API/inboxes/$ADDRESS/latest" \
-H "X-API-Key: $MAILFLAT_API_KEY" | jq '.email.spam'
# null ← not scanned. Treat it as "unknown", never as "clean".
Worth knowing
The score stays readable on encrypted inboxes
Subject and body are sealed on an end-to-end encrypted inbox, but the spam score and rule names are not: rule names carry no message content, and hiding the score would leave an encrypted inbox with no way to answer why mail was filtered. It sits at the same level as the sender address.
See also
Raw headers
Every header the sender set comes back as a dictionary, so you can assert on Message-ID, follow the Received chain, or prove which relay signed the mail.
Message propertiesThe envelope around the body: who sent it, exactly which address it reached, what the subject line said and when it landed.
Sending emailPoint it the other way: send real mail from a MailFlat address and check that your application handles what arrives.