Block Email Spam with Postfix and SpamAssassin Content Filter

In previous articles, we discussed how you can quickly set up your own mail server using iRedMail and  7 effective methods for blocking email spam with Postfix SMTP server. I’m going to share more tips and tricks to block email spam in this article. Specifically, we will see how to check email header and body with Postfix and SpamAssassin (SA) to detect spam. SpamAssassin is a free, open-source, flexible and powerful spam-fighting tool.

Email Header and Body Checks with Postfix SMTP Server

Postfix provides 4 simple content checking parameters.

  • header_checks
  • mime_header_checks
  • nested_header_checks
  • body_checks

Postfix will check all inbound emails when any of the above parameters is being used. Each parameter points to a lookup table containing regular expression patterns and actions. The patterns are compared to strings within email messages (header and body). If Postfix finds a match, the specified action is executed. Header and body checks are done by the Postfix cleanup daemon.

There are mainly two types of regular expressions that can be used by Postfix.

  • regexp: POSIX regular expression
  • PCRE: Perl compatible regular expression

Postfix comes with POSIX regular expression support, but PCRE is way faster. To use PCRE in Postfix, you need to install the postfix-pcre package.

sudo apt install postfix-pcre

Run the following command and you will see pcre is now supported.

postconf -m

postfix pcre perl compatible regular expression

Header Checks

To enable header_checks in Postfix, open the main configuration file.

sudo nano /etc/postfix/

Add the following line at the end of the file.

header_checks = pcre:/etc/postfix/header_checks

Save and close the file. Then you need to create the /etc/postfix/header_checks lookup file with a command line text editor such as Nano.

sudo nano /etc/postfix/header_checks

You can add regular expression checking like below.

/free mortgage quote/     REJECT
/repair your credit/      REJECT

The lefthand key is a regular expression enclosed by two forward slashes. If any of the strings on the left-hand appear in any of the headers of an email message (these would most likely show up in the Subject: header), the message is rejected during the SMTP dialog. By default regular expression checking is not case-sensitive.

You can also use DISCARD, instead of REJECT.

/free mortgage quote/    DISCARD
/repair your credit/     DISCARD

This will cause Postfix to claim successful delivery and silently discard the message. DISCARD makes it look as if the message was delivered even though it was simply thrown away. I often use DISCARD when I don’t want the spammer to know I have blocked a certain phrase for incoming email. DISCARD can also be useful to minimize the backscatter problem. If an innocent user’s email address is used as the sender address, you can claim successful delivery, so that the innocent user does not receive bounce messages.

Some stupid spammers use multiple email addresses in the To: header, instead of using Blind Carbon Copy (BCC). If you are sure an email address won’t be accepting emails with multiple recipients in the To: header, you can add the following lines to discard such email.

/To:.*(||outlook|*[email protected]/       DISCARD
/To:.*[email protected].*(||outlook|       DISCARD

The above lines will check if an Gmail/Yahoo/Outlook/Hotmail address and your domain email address are in the To: header at the same time. If true, the email will be discarded. The two characters .* are a wild card in regular expressions that can be matched to any characters.

You may also want to do the same with the Cc: (carbon copy) header, if you are sure an email address won’t be accepting emails with multiple recipients in the Cc: header

/Cc:.*(||outlook|*[email protected]/       DISCARD
/Cc:.*[email protected].*(||outlook|       DISCARD

Some spammers use blank email address in the From: or To: header, you can add the following checks.

/To:.*<>/           DISCARD
/From:.*<>/         DISCARD

Once you finish editing the header_checks lookup file, you need to build the index file.

sudo postmap /etc/postfix/header_checks

Then restart Postfix for the changes to take effect.

sudo systemctl restart postfix

Body Checks

In addition to header checks, Postfix can check the body of an email message. To enable body_checks in Postfix, open the main configuration file.

sudo nano /etc/postfix/

Add the following line at the end of the file.

body_checks = pcre:/etc/postfix/body_checks

Save and close the file. Then you need to create the /etc/postfix/body_checks lookup file.

sudo nano /etc/postfix/body_checks

You can add regular expression checking like below.

/free mortgage quote/     REJECT
/repair your credit/      REJECT

You can use DISCARD, instead REJECT.

/free mortgage quote/     DISCARD
/repair your credit/      DISCARD

The patterns indicated by the body_checks parameter are checked against each line of the body of the message. If any of the strings on the leftland appear in the body of an email message, the message is rejected or discarded. Once you finish editing the body_checks lookup file, you need to build the index file.

sudo postmap /etc/postfix/body_checks

Then restart Postfix for the changes to take effect.

sudo systemctl restart postfix

SpamAssassin Content Filter

The built-in content checking in Postfix is very simple. However, there is no way to whitelist individual messages that you might want to receive despite their containing phrases that trigger a rejection and you might not want to reject or discard an email message based on a single rule. For more sophisticated analysis, we need to use a dedicated content filter (such as SpamAssassin) specifically designed to detect spam.

SpamAssassin is a score-based system. It will check email message against a large set of rules, in contrast to a single rule in Postfix. Each rule adds or removes points in the message’s score. If the score is high enough (by default 5.0), the message is considered spam.

Install SpamAssassin

Note: If you used iRedMail to set up your mail server, then SpamAssassin is already installed alongside Amavis, which can read SpamAssassin rules. You don’t need to follow the instructions in this section.

Run the following command to install SpamAssassin from the default Ubuntu software repository. Spamc is the client for SpamAssassin spam filtering daemon.

sudo apt install spamassassin spamc

During the installation, the debian-spamd user and group will be automatically created. The binary installed by the spamassassin package is called spamd, which will be listening on TCP port 783 on local host.

spamassassin content filter

By default, the spamassassin systemd service is disabled, you can enable auto start at boot time with:

sudo systemctl enable spamassassin

Then start SpamAssassin.

sudo systemctl start spamassassin

Integrate SpamAssassin with Postfix SMTP Server as a Milter

There are several ways you can use to integrate SpamAssassin with Postfix. I prefer to use SpamAssassin via the sendmail milter interface, because it allows me to reject an email when it gets a very high score such as 8, so it will never be seen by the recipient.

Install the spamass-filter packages from Ubuntu default software repository.

sudo apt install spamass-milter

Next, edit /etc/postfix/ file and add the following lines at the end of the file.

# Milter configuration
milter_default_action = accept
milter_protocol = 6
smtpd_milters = local:spamass/spamass.sock
non_smtpd_milters = $smtpd_milters

If you have configured OpenDKIM and OpenDMARC, then these lines should look like below. The order matters.

# Milter configuration
milter_default_action = accept
milter_protocol = 6
smtpd_milters = local:opendkim/opendkim.sock,local:opendmarc/opendmarc.sock,local:spamass/spamass.sock
non_smtpd_milters = $smtpd_milters

If you haven’t configured OpenDMARC, then you should remove local:opendmarc/opendmarc.sock, from smtpd_milters.

Save and close the file. Now open the /etc/default/spamass-milter file and find the following line.


Uncomment this line and change 15 to your preferred reject score such as 8.


If the score of a particular email is over 8, Spamassassin would reject it and you would find a message like below in the /var/log/mail.log file, indicating it’s rejected.

milter-reject: END-OF-MESSAGE  5.7.1 Blocked by SpamAssassin

If you want the sender to see a different reject text, then add the -R (reject text) option like below.

OPTIONS="-u spamass-milter -i -R SPAM_ARE_NOT_ALLOWED_HERE"

Save and close the file. Restart Postfix and Spamass Milter for the changes to take effect.

sudo systemctl restart postfix spamass-milter

Note: iRedMail users need to start the SpamAssassin service in order to use spamass-milter.

sudo systemctl start spamassassin
sudo systemctl enable spamassassin

Checking Email Header and Body with SpamAssassin

SpamAssassin ships with many spam detection rules in /usr/share/spamassassin/ directory. Allow me to explain some of the rules.

In the /usr/share/spamassassin/ file, you can find the following two lines.

header MISSING_HEADERS       eval:check_for_missing_to_header()
describe MISSING_HEADERS     Missing To: header

The first line tests if the To: header exists in an email message. The second line, which is optional, explains what the first line does. The uppercase letters is the name of this test.

The following 3 lines are for testing if there’s a Date: header in the email message.

header __HAS_DATE            exists:Date
meta MISSING_DATE            !__HAS_DATE
describe MISSING_DATE        Missing Date: header

And these 3 lines are for testing if there’s a From: header in the email message.

header __HAS_FROM            exists:From
meta MISSING_FROM            !__HAS_FROM
describe MISSING_FROM        Missing From: header

You might want to use the Cron job shipped with SpamAssassin to automatically update SpamAssassin’s rules on a daily basis. If so, open the /etc/default/spamassassin file and change CRON=0 to CRON=1.

Set Custom Score for Existing Rules

In the and file, you can see the default scores for various tests. If you think the default score is too low or too high for a certain test, you can set custom score in /etc/spamassassin/ file.

sudo nano /etc/spamassassin/

For example, RFC 5322 requires that every email message must have From: and Date: header fields, so I can set a very high score if either of them is missing in an email message by appending the following two lines in file.

score MISSING_FROM   5.0
score MISSING_DATE   5.0

Although the To: header field is not mandatory in RFC 5322, I prefer to set a high score if it’s missing in an email message because I have never seen a legitimate email missing this header field.


Some spammers uses two email addresses in the From: header field like below.

From: "[email protected]" <[email protected]>

I think the default score for this kind of email is low, I prefer to set it to 3.0.

score PDS_FROM_2_EMAILS 3.0

There are spammers who send empty message with no subject and no textual parts in the body. I set the score for this kind of email to 5.0, so it will be placed to spam folder. Why read it if it’s empty?


And other spammers often ask you to send a read receipt, I set the score to 2.0 for this kind of email.


There are some spammers use different domain names in the From: and Reply-To: header, I give them a 3.5 score.


I also have seen some spammers using non-existent domain name in the From: header field. I set a 5.0 score for this type of email.


Last but not least, many spammers spoof the domain in the From: header field. I set a 2.5 score this kind of email.


Adding Your Own Rules

You can add custom SpamAssassin rules in /etc/spamassassin/ file.

sudo nano /etc/spamassassin/

Header Rules

For example, some spammers use the same email address in the From: and To: header, you can add the following lines at the end of the file to add scores to such emails.

header   FROM_SAME_AS_TO   ALL=~/\nFrom: ([^\n]+)\nTo: \1/sm
describe FROM_SAME_AS_TO   From address is the same as To address.
score    FROM_SAME_AS_TO   2.0

Some spammers use an empty address for the Envelope From address (aka the Return Path header). Although this is legitimate for sending bounce messages, I prefer to give this kind of email a score.

header    EMPTY_RETURN_PATH    ALL =~ /<>/i
describe  EMPTY_RETURN_PATH    empty address in the Return Path header.
score     EMPTY_RETURN_PATH    3.0

If you have configured OpenDMARC on your mail server, you can now add the following lines to add scores to emails that fail DMARC check.

header    CUSTOM_DMARC_FAIL   Authentication-Results =~ /dmarc=fail/
describe  CUSTOM_DMARC_FAIL   This email failed DMARC check
score     CUSTOM_DMARC_FAIL   3.0

The above code tells SpamAssassin to check if the Authentication-Results header contains the string “dmarc=fail”. If found, increase the score by 3.0.

Body Rules

You can tell SpamAssassin to increase the score of an email if a certain phrase is found in the body. For example, many spammers use the recipient’s email address in the first body line like below.

Hi [email protected]
Hello [email protected]
Dear [email protected]

I don’t want to talk with people who doesn’t bother writing my name in the first line of email. So I created a rule in SpamAssassin to filter this kind of email.

body      BE_POLITE       /(hi|hello|dear) xiao\@linuxbabe\.com/i
describe  BE_POLITE       This email doesn't use a proper name for the recipient
score     BE_POLITE       5.0

Regular expression in SpamAssassin is case-sensitive by default, you can add the i option at the end to make it case-insensitive.

Add Negative Scores

You can also add negative score to good emails, so there will be less false positives. For example, many of my blog readers ask me Linux questions and I don’t think spammers would include words like Debian, Ubuntu, Linux Mint in the email body, so I created the following rule.

body      GOOD_EMAIL    /(debian|ubuntu|linux mint|centos|red hat|RHEL|OpenSUSE|Fedora|Arch Linux|Raspberry Pi|Kali Linux)/i
describe  GOOD_EMAIL    I don't think spammer would include these words in the email body.
score     GOOD_EMAIL    -4.0

If the email body contains a Linux distro’s name, then add a negative score (-4.0).

There are some common phrases that is included in legitimate bounce messages, so I can add negatives scores to these email messages.

body      BOUNCE_MSG    /(Undelivered Mail Returned to Sender|Undeliverable|Auto-Reply|Automatic reply)/i
describe  BOUNCE_MSG    Undelivered mail notifications or auto-reply messages
score     BOUNCE_MSG    -1.5

Note that body rules also include the Subject as the first line of the body content.

Meta Rules

In addition to header and body rules, there’s also meta rules. Meta rules are combinations of other rules. You can create a meta rule that fires off when two or more other rules are true. For example, I occasionally receive emails saying that the sender wants to apply for a job and a resume is attached. I have never said on my website that I need to hire people. The attachment is used to spread virus. I created the following meta rule to filter this kind of email.

body      __RESUME        /(C.V|Resume)/i
meta      RESUME_VIRUS    (__RESUME && __MIME_BASE64)
describe  RESUME_VIRUS    The attachment contains virus.
score     RESUME_VIRUS    5.5

The first sub rule __RESUME tests if the email body contains the word C.V. or resume. The second sub rule __MIME_BASE64 is already defined in /usr/share/spamassassin/ file, as follows, so I don’t need to define it again in file. This rule tests if the email message includes a base64 attachment.

rawbody   __MIME_BASE64  eval:check_for_mime('mime_base64_count')
describe  __MIME_BASE64  Includes a base64 attachment

My meta rule RESUME_VIRUS will fire off when both of the sub rules are true, adding a 5.5 score to the email message. Note that sub rule often starts with double underscore, so it has no score in its own right.

Now you learned how to add score if a string is found. What if you want to add score when a string doesn’t exist in the email headers? Well, you can use the ! operator. For example, I have seen spammers using a single word in the From: address.  I added the following lines to score this kind of email.

header __AT_IN_FROM   From =~ /\@/
score NO_AT_IN_FROM   4.0

The first line checks if the @ sign exists in the From: header. The second line defines a meta rule, which fires off when !__AT_IN_FROM is true. !__AT_IN_FROM rule is the opposite of the first header rule, which means when there’s no @ sign in the From: address, the meta rule fires off.

You can also add the following lines to check if a dot exists in the From: address.

header __DOT_IN_FROM   From =~ /\./
score  NO_DOT_IN_FROM  4.0


You can use the whitelist_from parameter to add a particular email address or domain to your Spamassassin whitelist. For example, add the following two lines at the end of file.

whitelist_from [email protected]
whitelist_from *

A whitelisted sender has a -100 default score. They will still be tested by SpamAssassin rules, but it’s super hard for them to reach a 5.0 score.


To blacklist a sender, use the blacklist_from parameter, which has the same format as whitelist_from.

blacklist_from [email protected]
blacklist_from *

Checking Syntax and Restart

After saving the file. You should run the spamassassin command in lint mode to check if there’s any syntax errors.

sudo spamassassin --lint

Then restart SpamAssassin for the changes to take effect. (If you use Amavis with Spamassasin as in iRedMail, you just need to restart Amavis: sudo systemctl restart amavis)

sudo systemctl restart spamassassin

SpamAssassin’s Builtin Whitelist

It’s worth mentioning that SpamAssassin ships with its own whitelist. There are several files under /usr/share/spamassassin/ directory that includes 60_whitelist in the filename. These files contain SpamAssassin’s builtin whitelist. For example, the file contains a list of addresses which send mail that is often tagged (incorrectly) as spam.

Move Spam into the Junk Folder

Note: iRedMail has this configured out-of-the-box.

I’m going to show you how to move spam to Junk folder with the Dovecot IMAP server and the sieve plugin. This method requires that inbound emails are delivered to the message store via the Dovecot “deliver” LDA (local delivery agent). If you can find the following text in /var/log/mail.log file, then this requirement is satisfied.



delivered via dovecot service

Run the following command install dovecot-sieve from Ubuntu software repository.

sudo apt install dovecot-sieve

This package installs two configuration files under /etc/dovecot/conf.d/ directory: 90-sieve.conf and 90-sieve-extprograms.conf. Open the 15-lda.conf file.

sudo nano /etc/dovecot/conf.d/15-lda.conf

Add the sieve plugin to local delivery agent (LDA).

protocol lda {
    # Space separated list of plugins to load (default is global mail_plugins).
    mail_plugins = $mail_plugins sieve

Save and close the file. If you can find the 20-lmtp.conf file under /etc/dovecot/conf.d/ directory, then you should also enable the sieve plugin in that file like below.

protocol lmtp {
      mail_plugins = quota sieve

Edit the /etc/dovecot/conf.d/10-mail.conf file.

sudo nano /etc/dovecot/conf.d/10-mail.conf

Sieve scripts are stored under each user’s home directory. If you followed my PostfixAdmin tutorial and are using virtual mailbox domains, then you need to enable mail_home for the virtual users by adding the following line in the file, because virtual users don’t have home directories by default.

mail_home = /var/vmail/%d/%n

Save and close the file. Then open the 90-sieve.conf file.

sudo nano /etc/dovecot/conf.d/90-sieve.conf

Go to line 79 and add the following line, which tells Sieve to always execute the SpamToJunk.sieve script before any user-specific scripts.

sieve_before = /var/mail/SpamToJunk.sieve

Save and close the file. Then create the sieve script.

sudo nano /var/mail/SpamToJunk.sieve

Add the following lines, which tells Dovecot to move any email messages with the X-Spam-Flag: YES header into Junk folder.

require "fileinto";

if header :contains "X-Spam-Flag" "YES"
   fileinto "Junk";

Save and close the file. We can compile this script, so it will run faster.

sudo sievec /var/mail/SpamToJunk.sieve

Now there is a binary file saved as /var/mail/SpamToJunk.svbin. Finally, restart dovecot for the changes to take effect.

sudo systemctl restart dovecot

Set Message Maximum Size

By default, SpamAssassin does not check messages with attachments larger than 500KB, as indicated by the following line in the /var/log/mail.log file.

spamc[18922]: skipped message, greater than max message size (512000 bytes)

The default max-size is set to 512000 (bytes). A high value could increase server load, but I think the default size is a little bit small. To increase the max-size, edit /etc/default/spamass-milter file and add the following lines at the end.

#Spamc options
OPTIONS="${OPTIONS} -- --max-size=5120000"

The empty -- option tells spamass-milter to pass all remaining options to spamc, which understands the --max-size option. I increased the size to 5000KB. Save and close the file. Then restart spamass-milter.

sudo systemctl restart spamass-milter

How to Configure Individual User Preferences

You may want to set custom rules for emails sent to a specific address on the mail server. I like this feature very much. I have a contact email address for this blog, which is only used for keeping contact with readers. I don’t use the contact email address elsewhere, so I can create special spam-filtering rules that apply only to this contact email address.

First, edit the SpamAssassin main configuration file.

sudo nano /etc/spamassassin/

Add the following line to allow user rules.

allow_user_rules 1

Save and close the file. Next, edit the SpamAssassin environment file.

sudo nano /etc/default/spamassassin

Find the following line.

OPTIONS="--create-prefs --max-children 5 --helper-home-dir"

We need to change it to

OPTIONS="--create-prefs --max-children 5 --helper-home-dir --nouser-config --virtual-config-dir=/var/vmail/%d/%l/spamassassin --username=vmail"


  • --nouser-config: disable per-user configuration file for local Unix users.
  • --virtual-config-dir: specify the per-user configuration directory for virtual users. The %d placeholder represents the domain part of email address and %l represents the local part of email address.
  • --username: run spamd as the vmail user.

Save and close the file. Then restart SpamAssassin.

sudo systemctl restart spamassassin

By default, spamass-milter will send only the local part of email address to SpamAssassin. We need to make it send the full email address. Edit the spamass-milter configuration file.

sudo nano /etc/default/spamass-milter

Find the following line.

OPTIONS="-u spamass-milter -i -R SPAM_ARE_NOT_ALLOWED_HERE"

Add the following option to this line.


Like this:

OPTIONS="-e -u spamass-milter -i -R SPAM_ARE_NOT_ALLOWED_HERE"

The -e option will make spamass-milter pass the full email address to SpamAssassin. Replace with your real domain name. Save and close the file. Then restart spamass-milter.

sudo systemctl restart spamass-milter

Now send an email from Gmail, Hotmail, etc. to your domain email address. You will find the spamassassin directory is automatically created under /var/vmail/ directory.

cd /var/vmail/

You can use a command-line text editor to create the per-user preference file here. This file must be named as user_prefs.

sudo nano user_prefs

You can add custom rules in this file just as you would do in the /etc/spamassassin/ file.

For instance, I found many spammers end their email body with an unsubscribe link to let you remove future contact. I didn’t subscribe to their spam and I don’t think the unsubscribe link will remove my email address from their contact database. So I use SpamAssassin to score this kind of email.  The following rule adds 3.0 score to emails containing the word “unsubscribe” or its variations in the body. (I don’t use the contact email address of this blog to subscribe to anything online.)

body      SUBSCRIPTION_SPAM   /(unsubscribe|u n s u b s c r i b e|Un-subscribe)/i
describe  SUBSCRIPTION_SPAM   I didn't subscribe to your spam.
score     SUBSCRIPTION_SPAM   3.0

Sometimes the email body doesn’t contain the word “unsubscribe”, but there’s a List-Unsubscribe header, which means the spammer added my contact email address to their mailing list without my consent. I can score this type of email too, with the following rule.

header    LIST_UNSUBSCRIBE   ALL =~ /List-Unsubscribe/i
describe  LIST_UNSUBSCRIBE   I didn't join your mailing list.
score     LIST_UNSUBSCRIBE   2.0

I created a Mailjet account with a different email address. Some spammers assume that I used my contact email address to create Mailjet account, so they try to impersonate Mailjet customer service to lure me into typing my password at a fake Mailjet login page. I can score this kind of email like below.

header    MAILJET_IMPOSTER   From =~ /mailjet/i
describe  MAILJET_IMPOSTER   I don't have a mailjet account for this email address.
score     MAILJET_IMPOSTER   2.5

The above lines check if the From: header contains the word mailjet. If found, give it a 2.5 score.

I occasionally receive emails from Chinese spammers whose From: domain name has no vowel letters (a, e, i, o, u). The spammer used the domain name. It is nearly impossible for a normal person/entity to use domain names without vowel letters, considering that many top-level domains have already included vowel letters (.com, .net, .org, .co, .io, .shop, .dev, etc), so I give this kind of email a very high score like below. The default score is 0.5.


Some spam emails use many images in the body but contains very little text. The default score for this kind of email is 1.9, but I prefer to set a high score for my contact email address.

score HTML_IMAGE_RATIO_02 4.0

I also received a spam email with my email address in the subject, so I can add a high score to it.

header    SUBJECT_SPAM   Subject =~ /xiao\
describe  SUBJECT_SPAM   Subject contains my email address.
score     SUBJECT_SPAM   4.0

Some spammers use BCC (Blind Carbon Copy) to hide other recipients. I don’t want to receive such email. So I made the following rule. If my domain name is not in the To: header, add 3.0 to the email.

header __DOMAIN_IN_TO     To =~ /
score  DOMAIN_NOT_IN_TO   3.0

After adding custom rules, close the file and run the following command to check syntax. Silent output means there’s no syntax error.

sudo spamassassin --lint

Finally, restart SpamAssassin for the changes to take effect.

sudo systemctl restart spamassassin

Now you can test the user preferences by sending test emails from other email services to your own domain email address.

Whitelisting for Specific Email Addresses

Let’s say you have an email address that accepts emails from a few email addresses, and you want to block all other senders. It’s very easy to accomplish this. Go to /var/vmail/ directory and create the user_prefs file. Then add allowed email addresses to the whitelist.

whitelist_from *
whitelist_from [email protected]

Next, add all domains to the blacklist.

blacklist_from *

Save and close the file. Restart SpamAssassin for the changes to take effect.

sudo systemctl restart spamassassin

Reject or Bounce

If a receiving SMTP server determines during the SMTP conversation that it will not accept the message, it rejects the message. Sometimes the SMTP server accepts a message and later discovers that it cannot be delivered, perhaps the intended recipient doesn’t exist or there is a problem in the final delivery. In this case, the SMTP server that has accepted the message bounces it back to the original sender by sending an error report, usually including the reason the original message could not be delivered.

You should not bounce spam, because the email address in the Return-path: header or From: header probably doesn’t exist or is an innocent person’s email address, so the bounce message will probably go to an innocent person’s email address, creating the backscatter problem. Instead of bouncing the spam, you should reject spam during the SMTP dialog, before the email is accepted. This article didn’t show you bouncing a spam message. You should remember this rule in case you are going to create spam-filter rules by yourself. If in doubt, test your spam-filtering rules to see if it’s going to create bounce messages.


By default, SpamAssassin enables URIBL rule, which checks if an email message contains links that are identified as spam by URIBL. This is a very effective anti-spam measurement. However, you might be blocked from querying URIBL. Check the raw email headers of an inbound email message, find the X-Spam-Status header.

X-Spam-Status: No, score=-92.2 required=5.0 tests=DATING_SPAM,DKIM_SIGNED,
	autolearn=no autolearn_force=no version=3.4.2

If you can find URIBL_BLOCKED in this header, that means you are blocked from querying URIBL. Most of the time it’s because you are not using your own local DNS resolver. You can run the following command on your mail server to test which DNS server you are using to query URIBL.

host -tTXT

Sample output: descriptive text " -> Query Refused. See for more information [Your DNS IP: xx.xx.xx.xx]"

To fix this error, you need to run your own local DNS resolver on your mail server.

Once your local DNS resolver is up and running, test URIBL again.

host -tTXT

If you see the following output, it means you are now allowed to query URIBL. descriptive text "permanent testpoint"

From here on out, inbound email messages won’t have the URIBL_BLOCKED tag in the X-Spam-Status header.

Prevent Outgoing Spam

Postfix header and body checks can also be used to prevent outgoing spam. For example, if you don’t want your email server to send emails to a particular email address, you can add the following line to /etc/postfix/header_checks.

/^To:.*fake.*/       DISCARD

The above line tests if the To: header contains the fake word. If found, discard the email. I’m just showing a simple example here. This can be useful when you have a contact form or sign-up form on your website and some visitors use fake email addresses on the form to generate spam on your email server.

Save and close the file. Then run the following command to rebuild the hash table.

sudo postmap /etc/postfix/header_checks

Reload Postfix for the change to take effect.

sudo systemctl reload postfix

Deleting Email Headers For Outgoing Emails

You can use smtp_header_checks to delete email headers that could show sensitive information. smtp_header_checks are only applied when Postfix is acting as an SMTP client, so it won’t affect incoming emails.

For example, you might not want the recipient to know that you are using SpamAssassin on your mail server, then you can create the /etc/postfix/smtp_header_checks file

sudo nano /etc/postfix/smtp_header_checks

And add the following lines in the file. This tells Postfix to delete the X-Spam-Status and X-Spam-Checker-Version header from the email message when sending emails.

/^X-Spam-Status:/             IGNORE
/^X-Spam-Checker-Version:/    IGNORE

Save and close the file. Then edit the Postfix main configuration file.

sudo nano /etc/postfix/

Add the following line at the end of the file.

smtp_header_checks = pcre:/etc/postfix/smtp_header_checks

Save and close the file. Next, run the following command.

sudo postmap /etc/postfix/smtp_header_checks

Reload Postfix for the change to take effect.

sudo systemctl reload postfix

Other Tidbits

The spamassassin package on Debian/Ubuntu ships with a Cron job (/etc/cron.daily/spamassassin) to automatically update rulesets daily with the sa-update command.

SpamAssassin 4.0 includes a HashBL plugin, which can check if a Bitcoin address in the email body has been used by scammers. And there’s also a new plugin called “Ole Macro” that can check if an email contains an Office attachment with a macro. This plugin would try to detect if the attched macro is malicious or not.

Wrapping Up

I hope this tutorial helped you use Postifix and SpamAssassin to filter spam. As always, if you found this post useful, then subscribe to our free newsletter to get more tips and tricks. Take care 🙂

Rate this tutorial
[Total: 6 Average: 5]

75 Responses to “Block Email Spam with Postfix and SpamAssassin Content Filter

  • Marcio Merlone
    3 years ago

    Very good guide, although it is not wise to block/reject empty addresses, like From: . There is legitimate mail with that.

    • Yes, legitimate bounce messages and auto-reply messages can use empty MAIL FROM address (Return-Path: <>), but they still use a valid email address in the From: header.

      MAIL FROM is different than Header From. This article only shows rejecting empty Header From.

  • Nina Vise
    3 years ago

    Hey Xiao Guo An,

    very nice guide I just used the Part: Set Reject Score for my Plesk installation of dovecot and postfix, and it is working. Is there a way to change the Message “Blocked by SpamAssassin” to “Blocked by provider in case you are a human contact: xyz” ?

    Thank you very much!

    • Edit the /etc/default/spamass-milter file, add the -R (reject text) option like below.

      OPTIONS="-u spamass-milter -i -R your_custom_reject_text"

      Then restart spamass-milter.

      sudo systemctl restart spamass-milter

      Note that you can’t add space character in your reject text, or only the first word will be seen by the sender. I haven’t found a way to escape the space character yet, so I use underscore in place of space character.

  • t0m_Cr00z
    2 years ago

    Hi Bro Xiao Guo An,

    Is this ok to apply this with my current setup of iredmail? Which i also follow in one of your articles.

  • I was getting a lot of spam recently mostly from same template.
    I added the body_checks regex. let’s see if i get same spam again.
    thanks for the tutorial though.. 🙂

  • Hi Bro Xiao Guo An!
    I have this problem. When I try to execute query: doveadm expunge -A mailbox Junk all, I get this error – Error: User listing returned failure

    doveadm: Error: Failed to iterate through some users.

    In auth-sql.conf.ext

    passdb {
      driver = sql
      args = /etc/dovecot/dovecot-sql.conf.ext
    userdb {
      driver = static
      args = uid=vmail gid=vmail home=/var/mail/vhosts/%d/%n

    and i have

    iterate_query = SELECT email AS user FROM virtual_users; 

    in /etc/dovecot/dovecot-sql.conf.ext But requests are executed with an error

    dovecot - auth: Error: Trying to iterate users, but userdbs don't support it
    • If you are using virtual mailbox with MySQL/MariaDB, then the driver should be set to sql.

      userdb {
        driver = sql
        args = /etc/dovecot/dovecot-sql.conf.ext
  • Guy Merritt
    2 years ago

    I was very excited to find a word, or string-based, content filter for Postfix. After using Qmail for years, I had to make a switch on my system as Qmail is not maintained and won’t even compile on modern Linux systems, now. Spamassassin works fine, but when I add the header_checks and body_checks lines to, my Postfix system goes into fits – it seem that some sort of loop is being created, plus, I get an error email, part of which says:

     Out: 250 2.1.5 Ok
     In:  DATA
     Out: 354 End data with .
     Out: 451 4.3.0 Error: queue file write error
     In:  QUIT
     Out: 221 2.0.0 Bye

    I’m an old guy – 68 in 10 days – and I’m not smart enough to have any clue as to why this is happening. If I send one email with these features enabled, I get an error email – like the one I’ve posted – about every 3 minutes, in my inbox. If I remove those lines and reload Postfix, everything works fine. Any idea why this might be happening? I did add a line to increase the size limit of the queue to something huge, but that didn’t change this weird behavior I’m seeing when I attempt to use these features.

    • Guy Merritt
      2 years ago

      Okay……found ONE page on the web that suggested a different syntax and now everything is working perfectly – I had to use the following syntax for the entries on the bottom of the /etc/postfix/ file:

      body_checks = regexp:/etc/postfix/body_checks

      I’m a dummy – have no idea what the “regexp” means, nor, does. But my mail server, now, either dumps (discards) filtered email in both the body and head….or it will reject the offending emails and notify the sender. No errors, no issues when I use this syntax.

    • Perhaps it’s because your system doesn’t have PCRE map support for Postfix. on Debian and Ubuntu, you can run the following command to install it.

      sudo apt install postfix-pcre
  • Guy Merritt
    2 years ago

    I had followed your “recipe” and, indeed, had installed postfix-pcre. I also ran the command (don’t recall the command) to see if pcre was available to Postfix – the results listed Postfix as having pcre as an available component. So, heck – I don’t know why it didn’t work. But your article is excellent, anyway. Thanks for putting such good info together for the “slow” people, like me! Anyway, in the event that things don’t work in the way you’ve suggested the method I found is a completely workable alternative. Thanks again for your good work.

  • Guy Merritt
    2 years ago

    How can a person support your work…?? Is there a place to donate?

  • Guy Merritt
    2 years ago

    Well… won’t be able to retire on a whopping 20 bucks, but – hey – it’s the end of the month!!!!!

  • Thank your for all the great tutorials… I’ve used several of them. In my old mail server I used regexp for my header_checks so I love the speed increase of using pcre. My question has to do with comments within the header_checks file. when I was using regexp I would add something like: “# rule to stop annoying diet pill emails”.

    # /^HEADER: .*content_to_act_on/ ACTION
    # /^Subject .*Free Money/ REJECT Spam Header Rule #42
    /^Subject: .*Make Money Fast*/ REJECT
    /^Subject: .*Dr\. Oz*/ REJECT

    Will it cause a problem if I include comments with # in front of them?

    • A line starting with # is a comment and the remaining regex in the line is ignored.

      It’s better to place the comment on its own line.

      # Spam Header Rule #42
      /^Subject .*Free Money/    REJECT
      • Thank you for your help. I really appreciate your explanations within your tutorials instead of just telling us what setting to use. Its nice to know what each setting does so we can make intelligent decisions about how the pieces go together.

  • – Is there any advantage to upgrade those components – ubuntu, dovecot, postfix, postfiadmin, nginx, openssl, to the latest versions?
    – If yes how can we perform those tasks?

    • If you don’t know any advantage of using latest version, then you should use the default version in Ubuntu repository. I only use the latest version when it has a new feature that I really need.

      • But If We leave this installation as is, very soon it will become older and useless. Any installation needs a plan to have the most reliable versions. That’s what I was talking about and asking for, thank you.

    • Ubuntu LTS is supported for 5 years. It is reliable and not going to be useless anytime soon. When the next Ubuntu LTS comes out, simply follow a tutorial to upgrade to it.

      • What about the other components – dovecot, postfix, nginx, MariaDB, PHP, … -? It is advised to upgrade to the latest versions OR the actual versions are good for the next 5 years as ubuntu LTS?

    • Both the system and any software in the repository are supported for 5 years.

      You don’t need to upgrade every software to the latest version on a production server.

      Actually it’s a bad practice to use the latest version for every software on a production server. Latest version is usually less reliable than previous versions.

  • Vitorpt
    2 years ago

    Hi, have a doubt regarding this rule
    /free mortgage quote/ DISCARD

    Can I use one like this
    /free mortage/quote/ DISCARD


  • michael rowe
    2 years ago

    Great Article. Incorporated a lot of it. It put a lot of things all in one place. Thanks

  • Raffael Carvalho
    2 years ago

    Hello! Due to an AWS SES restriction I need to create a routine to block the sending of email to email addresses that have marked the receipt of email from my server as spam. The ideal would be to block only the sending by the recipient of the email returned as spam.
    This is my first email server, and I practically did everything following your tutorials (postfix / dovecot / roundcube). Can you give me a tip on how to create this block / rule?

  • Erik van de Stolpe
    2 years ago

    Hi Xiao, First of all: a big THANK YOU for your mailserver series (from scratch, Ubuntu). In this last episode of the series, you tell us in the “Integrate SpamAssassin with Postfix SMTP Server as a Milter” parapraph that we should now have the following line for the smtpd_milters if the series was followed:

    smtpd_milters = local:opendkim/opendkim.sock,local:opendmarc/opendmarc.sock,local:spamass/spamass.sock

    I followed the series, but I don’t recall having seen any addition of local:opendkim/opendkim.sock or local:opendmarc/opendmarc.sock. Mine was empty. Upon adding the suggested line, sending mail stopped working with the error:

    sieve script file path ~/.dovecot.sieve is relative to home directory, but home directory is not available.

    So I quickly reverted to my snapshot to get things working again.

    Did I miss something here?

    • If you followed part 3, you should have the opendkim milter.

      And this OpenDMARC tutorial shows you how to integrate OpenDKIM with Postfix via the milter interface.

      You can set a home directory for mail users in /etc/dovecot/conf.d/10-mail.conf file. Add the following line in this file.

      mail_home = /var/vmail/%d/%n

      Then restart dovecot.

      sudo systemctl restart dovecot

      PS: I didn’t set the home direcory for mail users on my mail server, and my sieve scripts are working fine.

      • Erik van de Stolpe
        2 years ago

        Apart from having a flu, I must have been drunk. I’ll go over the whole series once more to find if I missed more.

  • Hi,
    It was all working well until I added the spam assassin and connected plugins in. Now my emails are not getting through. My gmail acct. was getting through before.
    These are the errors in my mail.log file.
    I see from the last comment the person before left which sounded similar. Although I have followed yours from start to finish.
    Here is my amended log file:

    Mar 31 18:05:20 mail dovecot: auth: Debug: userdb out: USER#0111#011[MYEMAIL]#011maildir=[MYDOMAIN]/[USR]/#011uid=2000#011gid=2000
    Mar 31 18:05:20 mail dovecot: lmtp(MYEMAIL): Error: User [EMAIL] doesn't have home dir set, disabling duplicate database
    Mar 31 18:05:20 mail dovecot: lmtp(MYEMAIL): Error: sieve: file storage: Sieve storage path `~/sieve' is relative to home directory, but home directory is not available.
    Mar 31 18:05:20 mail dovecot: lmtp(MYEMAIL): Error: sieve: Failed to access user's personal storage (temporary failure)
    Mar 31 18:05:20 mail dovecot: lmtp(12805): Disconnect from local: Client has quit the connection (state=READY)
    Mar 31 18:05:20 mail postfix/lmtp[12803]: 962C5817A7: to=, relay=[SERVER][private/dovecot-lmtp], delay=12965, delays=12965/0.04/0.03/0.09, dsn=4.2.0, status=deferred (host [SERVER][private/dovecot-lmtp] said: 451 4.2.0  Temporarily unable to access necessary Sieve scripts (in reply to end of DATA command))

    Any help would be gratefully received

    • Hello Xiao Guoan – I really appreciate that you look through and give real answers to your readers. So thanks…
      so hopefully on my other recent comments you can see I really try to resolve my issues to stop you trying to answer my solved problems. I think I have ‘temporarily’ resolved the issue…

      At this point the I have been unsuccessfully sending through the day (messages I thought lost) are pinging through. removing sieve from this command:
      The only change I can think I made that could have solved it is either removing sieve from this command:

      protocol lmtp {
            mail_plugins = quota sieve

      or removing a MX 5 line from my DNS (this was to allow the fail2ban protocols in your ‘7 effective tips…’ page

    • Your mail server’s problem is that there’s no home directory for virtual users. Sieve scripts are stored under each user’s home directory. If you followed my PostfixAdmin tutorial and are using virtual mailbox domains, then you need to enable mail_home for virtual users by adding the following line in the file, because virtual users don’t have home directories by default.

      mail_home = /var/vmail/%d/%n

      Save and close the file. And restart Dovecot.

      sudo systemctl restart dovecot

      Now you can re-enable sieve filter.

      • Hi I added this line (as per your other article), but for me, it doesn’t work. I changed it to:

        mail_home = maildir:/var/vmail/%d/%n

        This gets rid of most errors except the sieve command.

      • Sorted – Thanks…

  • Firstly, thanks for an incredible resource this has been so helpful getting things set up.

    I seem to be having problems with spamassassin though. I’m running Ubuntu 18.04 and then followed your instructions to install iRedMail and then all the other elements.

    When I receive an email I get the following error:

    spamd[14648]: plugin: eval failed: bayes: (in learn) locker: safe_lock: cannot create lockfile /var/vmail/[MyDomain]/[Username]/spamassassin/bayes.mutex: No such file or directory

    The /var/vmail directory structure I’ve got doesn’t match the one in your example, mine is /var/vmail/vmail1/[MyDomain]/[X]/[Y]/[Z]/[Username]-[date time]/ where X, Y & Z are the first three characters of the Username. Below the Username folder are Maildir and sieve directories.

    If you can sort this out for me there’ll efinitely be a couple of cappuccinos in it.

    Many thanks

  • Ron Jones
    2 years ago

    Excellent work!

    Does the latest iteration of spamassassin automatically learn the spam tokens? Or do we need to set up a cron job for sa-learn?

    Like this example from a previous server of mine:

    11 03 7,14,21,28 * * /usr/bin/sa-learn --spam "/var/vmail///.Junk/cur/"

    Followed by another that empties the spam folder after learning this week’s spam:

    31 03 7,14,21,28 * * cd /var/vmail///.Junk/cur/ && /bin/rm -f *

    Thanks again,

    • In my entry above, I used carat marks to enclose domain & username, and I don’t think the comment system liked that.

      Both examples should read as follows.

      • I am also looking forward to the answer. @Ron, have you tried or found anything out in the last weeks?


  • peacecop kalmer:
    2 years ago

    You suggested to reject messages with the same “To” and “From” address. Sometimes, I subscribe for a tennis competition and then they send a schedule exactly that way. If I set up this rule, I’d never see their schedule mails!

  • Kevin Hanrahan
    1 year ago

    I got an error when mapping the smtp_header_checks because I had already created a map when I installed Roundcube. That command was regex: and this one is pcre:. Can they both run under pcre?

  • Hi
    for me in /etc/default/spamass-milter
    the following line is not working

    OPTIONS="-e -u spamass-milter -i -R SPAM_ARE_NOT_ALLOWED_HERE"

    I had to use

    OPTIONS="-e -u spamass-milter -i"
  • Thank you sir.

    Today I do the last part of your tutorial (SpamAssassin part).
    so far everything is working fine.

    Only 2 things I had to change:

    The package of Dovecot in Debian 10 is installed with wrong permissions.
    I set the good permissions with

    chmod -R 755 /etc/dovecot/

    second thing was to solve this error:

    doveadm: Error: Failed to iterate through some users

    when using “doveadm expunge -A … ” commands.
    answer was given by you in comment section of the SpamAssassin thread
    in /etc/dovecot/conf.d/auth-sql.conf.ext
    I have to change

    userdb {
     driver = static
     args = uid=vmail gid=vmail home=/var/mail/vhosts/%d/%n


    userdb {
     driver = sql
     args = /etc/dovecot/dovecot-sql.conf.ext

    now everything work flawlessly


  • The Americans
    1 year ago

    Fantastico Xiao!

  • George
    1 year ago

    Hi Xiao,

    Thanks for this is great.

    Is there a way to add text to the spam email body?
    Similar to what goolgle does when they send the emailt o spam folder?

  • Great ! Thanks !

  • On my debian10 I got always errors about local:opendkim/opendkim.sock or local:/opendkim/opendkim.sock

    File not found…

    I asked in google about that error and found the following:

    smtpd_milters = unix:opendkim/opendkim.sock,unix:opendmarc/opendmarc.sock,unix:spamass/spamass.sock

    I changed local to unix and the errormessages are away.

  • Hello everyone,
    while installing Spamassassin I get an error when the installer runs ‘sa-compile’. The command cannot find the folder ‘compiled’. But when I run ‘sa-update’ and ‘sa-compile’ manually the folder will be created. After that Spamassassin can be launched and runs flawlessly, but when installing the ‘spamass-milter’ the ‘compiled’ directory is removed and the error comes up again.
    Some additional information:
    – I am running Ubuntu 20.04
    – apt install XX runs as root
    – Output:

    Reading package lists... Done
    Building dependency tree       
    Reading state information... Done
    spamass-milter is already the newest version (0.4.0-1build1).
    0 upgraded, 0 newly installed, 0 to remove and 25 not upgraded.
    2 not fully installed or removed.
    After this operation, 0 B of additional disk space will be used.
    Do you want to continue? [Y/n] Y
    Subroutine File::Spec::Unix::canonpath redefined at /usr/share/perl/5.30/ line 111,  chunk 3.
    Subroutine File::Spec::Unix::catdir redefined at /usr/share/perl/5.30/ line 111,  chunk 3.
    Subroutine File::Spec::Unix::catfile redefined at /usr/share/perl/5.30/ line 111,  chunk 3.
    Setting up sa-compile (3.4.4-1ubuntu1) ...
    Running sa-compile (may take a long time)
    Warning: Fake start-stop-daemon called, doing nothing.
    chmod: cannot access '/var/lib/spamassassin/compiled': No such file or directory
    dpkg: error processing package sa-compile (--configure):
     installed sa-compile package post-installation script subprocess returned error exit status 1
    Setting up spamass-milter (0.4.0-1build1) ...
    usermod: no changes
    Job for spamass-milter.service failed because the control process exited with error code.
    See "systemctl status spamass-milter.service" and "journalctl -xe" for details.
    invoke-rc.d: initscript spamass-milter, action "start" failed.
    * spamass-milter.service - LSB: milter for spamassassin
         Loaded: loaded (/etc/init.d/spamass-milter; generated)
         Active: failed (Result: exit-code) since Sun 2020-12-20 17:12:27 CET; 16ms ago
           Docs: man:systemd-sysv-generator(8)
        Process: 507769 ExecStart=/etc/init.d/spamass-milter start (code=exited, status=1/FAILURE)
    Dec 20 17:12:26 systemd[1]: Starting LSB: milter for spamassassin...
    Dec 20 17:12:26 spamass-milter[507769]: Starting Sendmail milter plugin for SpamAssassin:
    Dec 20 17:12:26 spamass-milter[507792]: Warning: Fake start-stop-daemon called, doing nothing.
    Dec 20 17:12:27 spamass-milter[507795]: chmod: cannot access '/var/spool/postfix/spamass/spamass.sock': No such file or directory
    Dec 20 17:12:27 systemd[1]: spamass-milter.service: Control process exited, code=exited, status=1/FAILURE
    Dec 20 17:12:27 systemd[1]: spamass-milter.service: Failed with result 'exit-code'.
    Dec 20 17:12:27 systemd[1]: Failed to start LSB: milter for spamassassin.
    dpkg: error processing package spamass-milter (--configure):
     installed spamass-milter package post-installation script subprocess returned error exit status 1
    Errors were encountered while processing:
    E: Sub-process /usr/bin/dpkg returned an error code (1)

    I have also tried to run the installation from another user with sudo privileges with the same outcome.
    I would appreciate some help.


  • Hi Xiao
    Excelent guide here!!! will appreciate if u can help me with this new (for me) spam mode..i get mails with —SPAM— subject on my inbox (thats minnor issue) and the are with my own address in from and to lines so my mx wont detect them…How can i
    1.- get the SPAM emails go directly to SPAM folder
    2.- disable on my iredmail the possibility of any mailbox to send an email to its own mailbox, get it? (from: [email protected] to: [email protected]

    Lots of thanks!!!

  • Hi Xiao,

    Would you be able to advise the way I can go in order to implement next:

    1. I need to set mail content filter that lets me to check email with specific Words in the topic and then based on those it should reject the email and then send notification to the sender and receiver that email with such and such Topic was rejected. I see that I can apply the Emails header checks in my case but how do I send notification to sender and receiver?


  • Hi Xian,

    Would it work to accomplish this –

    “Email servers are configured to block, log and report emails with inappropriate protective markings.”
    “The intended recipients of any blocked inbound emails, and the sender of any blocked outbound emails, are notified.”

    Build a test mailserver using docker-mailserver container, and develop a content filter plugin that blocks, logs and reports messages with SEC=SECRET or SEC=TOP SECRET in the subject line. Notify both sender and recipient without disclosing the content of the message.


    10 months ago

    On this step, when I used this command:

    cd /var/vmail/

    I didn’t see spamassassin directory, only this one:

    [email protected]:/var/vmail/ ls -l
    total 4
    drwx------ 8 vmail vmail 4096 Mar 14 10:43 Maildir
    [email protected]:/var/vmail/

    Can you help me again Xiao? Thank you

  • Hi ! the spam message can’t move to junk !!
    Error: sieve: msgid=: failed to store into mailbox
    ‘Junk’: Mailbox doesn’t exist: Junk

  • Ary Angeli Mainart
    9 months ago

    Hi Xiao,

    Great post.
    I do follow all instructions Here:
    All working, but on mail.log i am having this warning:

    mail postfix/smtpd[82310]: warning: connect to Milter service local:opendmarc/opendmarc.sock: Permission denied

    Any Idea what could it be?
    I do follow every step again and double chek conf files and it matches with your post.

    I have another question:
    Whats the differences betwen those tutorials?
    Should I do follow both?

    If i follow this:
    Do i need to follow this other:


    • 1. You need to set up OpenDMARC.

      2. Postfix blocks spam at the SMTP level. SpamAssassin is a content filter that do heavy checks on the email headers and body. They are complementary to each other.

      3. You don’t need to follow the Ubuntu 16.04 SpamAssassin guide.

  • If integrating SpamAssassin with postfix via milter rejects the mail, how can dovecot move it to the junk folder like described later? It will never see it? I just want the email be moved to JUNK, never rejected or deleted without the users knowledge.

    • You can set a very high reject score like 100, or 1000, in the /etc/default/spamass-milter file.

      #OPTIONS="${OPTIONS} -r 100"

      So it’s highly unlikely that an incoming email will be rejected by SpamAssassin.

      • That makes no sense. Why would i install spamassassin and then configure it not to work? I need spamassassin to set the X-Spam header, so dovecot would move it to JUNK. But dovecot cannot move it because postfix already rejected it, because spamassassin runs as a milter. Or did i get something wrong? Cann dovecot move an email to junk even postfix already rejected it? That would be weird.

    • Just because SpamAssassin runs as a milter doesn’t mean Postfix will reject an incoming email.

      The spam score must reach the threshold before Postfix can reject it.

      If you set a very high score like 100, then it’s equivalent to never reject an email by its spam score.

      Setting a very high score doesn’t mean SpamAssassin stops working. There are two scores: spam score and reject score.

      Spam score will put an email to Junk folder.
      Reject score will reject an email.

      We are setting a very high reject score. The spam score stays the same.

  • Hello Xiao,

    After doing “Move Spam into the Junk Folder”, it starting appears this error in mail.log

    "spamd[]: plugin: eval failed: bayes: (in learn) locker: safe_lock: cannot create tmp lockfile /nonexistent/.spamassassin/bayes.lock.[mail server].13135 for /nonexistent/.spamassassin/bayes.lock: No such file or directory"

    looks like SPAMD does not have permission on /var/vmail

    /var/vmail is vmail:vmail … and permissions are 0700 for the most hierarchy

    Any help will be great…

    Kindest Regards, Alex

    • This error can occur if you don’t use virtual mailboxes (PostfixAdmin).

      SpamAssassin will create a .spamassassin directory for all users under the home directory when your mail server receives email messages, including the nobody user. Since the nobody user’s home directory is /nonexistent/, an error would occur because the nobody user can’t create the /nonexistent/ directory.

      This error doesn’t affect the operation of your mail server. If you use virtual mailbox, then this error would disappear. You can also try to fix the error by manually creating the directory.

      sudo mkdir -p /nonexistent/.spamassassin/
      sudo chown nobody:nogroup /nonexistent/.spamassassin/

      If the error still exist, be sure to follow all of this tutorial, especially the “Configure Individual User Preferences” section, which shows you how to disable per-user configuration file for local Unix users.

  • Hello Xiao Guoan, Your email tutorials have saved me a lot of time and they have helped me out a lot but I am experiencing a problem.

    I have setup a basic Postfix/Dovecot server with LMTP and IMAP, I also setup Postfixadmin using your tutorial and Roundcube aswell and also SPF/DKIM/DMARC, but when I send a basic spam email to my server it doesn’t move it to the Junk folder, it simply rejects it.

    I have checked the mail logs and It seems as if Spamassassin rejects the email before Dovecot has a chance to look at it.

    Is there a way to fix this?

    Sincerely, Abdul

    • ” Spamassassin rejects the email before Dovecot has a chance to look at it.”

      That’s how it should work. If an email’s spam score reaches the reject threshold, it will be rejected, never sent to Dovecot.

      Let’s say the spam score of an incoming email is 7, and the reject threshold is set to 10, then it will be put into the Junk folder. If the spam score of an incoming email is 11, it will be rejected.

      • Oh that explains why…

        I was using GTUBE which set the emails score above 1000.
        Well thanks for explaining.

  • Fernando
    8 months ago

    Hello, I have a question, if I have installed a certificate on my mail server, I have configured the server as the steps you gave above, as spamassassin detects the messages if they are encrypted? Can you tell me what is the path that makes a mail message having installed the same services as in your post?

  • Hi!

    I followed the tutorial step by step, installing SpamAssassin, Postscreen, Amavis and ClamAV. Everything is working like a charm and I couldn’t be happier. Now, on the bad note, one of the users got hacked (poor password) or has a virus in its local computer and the server started sending spam emails (around 150,000 per hour). I disabled that user and cleared the MailQ to prevent any more spam going out.

    My question is: Is there a way to connect SpamAssassin to check the outbound email as well? I know I could just add it to the content_filter of the “submission” rule, but this would replace smtp-amavis and I wish both to scan my outbound email to prevent this kind of things from happening again.

    If the answer is “No”, I could just disable SpamAssassin and enable the Amavis spam scanner, right? (@bypass_spam_checks_maps)

Leave a Comment

  • Comments with links are moderated by admin before published.
  • Your email address will not be published.
  • Use <pre> ... </pre> HTML tag to quote the output from your terminal/console.
  • Please use the community ( for questions unrelated to this article.
  • I don't have time to answer every question. Making a donation would incentivize me to spend more time answering questions.

The maximum upload file size: 2 MB. You can upload: image. Links to YouTube, Facebook, Twitter and other services inserted in the comment text will be automatically embedded. Drop file here