For those of you who are still masochistic enough to run your own mail servers, here’s what mine looks like now, with some commentary on my discoveries:

# to test: spamassassin --lint
# test msg: spamassassin -t < MSG
# rule desc: grep -h "^describe.*XYZ" /var/lib/spamassassin/*/*/*.cf

time_limit 300
report_safe 0
add_header all Report _REPORT_

# This seems to do nothing:
report_wrap_width 255

# This improves X-Spam-Status but makes X-Spam-Report unreadable:
# fold_headers 0

# Don't query these guys:
dns_query_restriction deny     # Many false positives
dns_query_restriction deny       # Many false negatives
dns_query_restriction deny       # Many false negatives
dns_query_restriction deny      # Blocked me?
dns_query_restriction deny       # Blocked me?

# Technical
score MISSING_MID               5       # Missing Message-Id: header
score MISSING_DATE              5       # Missing Date: header
score SPF_NONE                  2       # sender doesn't publish SPF
score SPF_SOFTFAIL              2       # sender doesn't match (softfail)
score SPF_FAIL                  3       # sender doesn't match (fail)
score DMARC_MISSING             2       # Missing DMARC policy
score DMARC_NONE                2       # DMARC none policy
score DMARC_REJECT              4       # DMARC reject policy
score DMARC_QUAR                4       # DMARC quarantine policy
score DKIM_ADSP_CUSTOM_MED      2       # No sig, adsp_override is CUSTOM_MED
score DKIM_ADSP_DISCARD         4       # No sig, domain suggests discarding
score FORGED_GMAIL_RCVD         3       # From gmail does not match 'Received'
score SPOOF_GMAIL_MID           2       # Not really from Gmail
score FREEMAIL_FORGED_REPLYTO   4       # Freemail in Reply-To, but not From
score FREEMAIL_FORGED_FROMDOMAIN 3      # 2nd level in From and Envelope differ
score SPOOFED_FREEM_REPTO       2       # Forged freemail w/ freemail reply-to
score MISSING_HEADERS           4       # Missing To: header
score FROM_MISSP_EH_MATCH       3       # From misspaced, matches envelope
score PDS_FRNOM_TODOM_NAKED_TO  4       # Naked to From name equals to Domain
score DATE_IN_FUTURE_Q_PLUS     2       # Date: 4 months after Received: date
score FROM_FMBLA_NEWDOM         4       # domain registered in last 7 days
score FROM_FMBLA_NEWDOM14       3       # domain registered in last 14 days
score FROM_FMBLA_NEWDOM28       3       # domain registered in last 28 days
score HEADER_FROM_DIFFERENT_DOMAINS 1   # From/Envelope 2nd level differ
score FROM_IN_TO_AND_SUBJ       2       # From address is in To and Subject
score FREEMAIL_REPLYTO          3       # Reply-To/From differ
score HTML_FONT_LOW_CONTRAST    1       # HTML color identical to background

# Content
score HK_NAME_MR_MRS            4       # I am Mr.
score FORM_FRAUD_5              3       # Fill a form and many fraud phrases
score ADVANCE_FEE_4_NEW         3       # Nigerian 419
score NA_DOLLARS                2       # Talks about a million dollars
score MONEY_FREEMAIL_REPTO      3       # Lots of money from free email
score LOTS_OF_MONEY             1       # Huge... sums of money
score ADVANCE_FEE_3_NEW_MONEY   3       # Advance Fee fraud and lots of money
score URG_BIZ                   3       # Contains urgent matter
score HOSTED_IMG_DIRECT_MX      4       # Image CDN, message direct-to-mx
score FSL_BULK_SIG              2       # Bulk signature with no Unsubscribe
score FROM_EXCESS_BASE64        2       # From: base64 encoded unnecessarily
score BITCOIN_TOEQFM            4       # Bitcoin + To same as From
score PRICES_ARE_AFFORDABLE     2       # Prices aren't too expensive
score T_PDS_FROM_2_EMAILS       2       # From header has multiple addresses
score BAD_CREDIT                2       # Eliminate Bad Credit
score NUMERIC_HTTP_ADDR         3       # Numeric IP address in URL
score FREEMAIL_REPLY            3       # From/body different freemails
score URI_WP_HACKED             3       # Compromised WordPress site
score UNDISC_FREEM              3       # Undisclosed recipients + freemail

# Reject mail that is obviously in a language other than English.
# This is supposed to be a one liner, but instead it is this big
# mess because none of this works as documented.
if plugin(Mail::SpamAssassin::Plugin::TextCat)
  # add an X-Spam-Language header
  add_header all Language _LANGUAGES_

  # Nope: TextCat doc says this will fire but it does not:
  #  ok_languages en

  # Nope: ok_locales doc says these will fire but they do not:
  #  ok_locales en
  #  score CHARSET_FARAWAY 3

  # This works, but only not when run under Postfix, not by "spamassassin -t".
  score    __HAS_LANGUAGE_1 0.01
  describe __HAS_LANGUAGE_1 Language was identified
  header   __HAS_LANGUAGE_1 X-Languages =~ /../
  score    __BAD_LANGUAGE_1 0.01
  describe __BAD_LANGUAGE_1 Not English
  header   __BAD_LANGUAGE_1 X-Languages !~ /\b(en)\b/
  meta       BAD_LANGUAGE   (__HAS_LANGUAGE_1 && __BAD_LANGUAGE_1)
  score      BAD_LANGUAGE   4
  describe   BAD_LANGUAGE   Not English
  add_header all Language Plugin Not loaded

# Custom regexps. I wish there were more concise ways than this.
# Rules beginning with __ are hidden from X-Spam-Report.
# Sub-rules of meta rules can't have score exactly 0 or they don't match.
score    __SUBJ_SPAM_1   0.01
meta       SUBJ_SPAM     (__SUBJ_SPAM_1)
score      SUBJ_SPAM     5
describe   SUBJ_SPAM     Spammy subject
describe __SUBJ_SPAM_1   Spammy subject

score    __BODY_SPAM_1   0.01
meta       BODY_SPAM     (__BODY_SPAM_1)
score      BODY_SPAM     5
describe   BODY_SPAM     Spammy body
describe __BODY_SPAM_1   Spammy body

score    __UNSUB_HEADER  0.01
score    __BODY_UNSUB_1  0.01
score    __BODY_UNSUB_2  0.01
meta       UNSUB         (__UNSUB_HEADER || __BODY_UNSUB_1 || __BODY_UNSUB_2)
score      UNSUB         4
describe   UNSUB         Message has unsubscribe stuff
describe __UNSUB_HEADER  List-Unsubscribe header present
describe __BODY_UNSUB_1  HTML with unsubscribe link
describe __BODY_UNSUB_2  Body has unsubscribe text

rawbody __BODY_UNSUB_1   />\s*Unsub/i

header  __SUBJ_SPAM_1    Subject =~ /Google Reviews|Quick Question|OneDrive/i

header  __UNSUB_HEADER   List-Unsubscribe =~ /./
body    __BODY_UNSUB_2   /Reply with STOP|receive any.?more email|please reply to this email|Click here to un|from future mailing|receive these email|you may unsub/

body   __BODY_SPAM_1     /optimize your business|attract more customers|best phone number|Dear Valued|brief survey|pre-paid card/i

# ...etc...

# Default for this is -6, which is probably too low.

It seemed wrong to me that SpamAssassin was detecting DMARC_REJECT but Postfix wasn’t just rejecting those outright, so I briefly tried installing SPF-Engine and I can now recommend that you do that only if your intent is that you are never able to send or receive email again.

My site is free of ads and trackers. Was this post helpful to you? Why not BuyMeACoffee


  1. Apache SpamAssassin
  2. Postfix