Rspamd with Postfix and Dovecot in Debian Stretch

As far as spam filters go, SpamAssassin isn’t terrible – but it’s slow and chews through more CPU than I’d like. As I needed to set up a new mail server, I decided to look for alternatives – and after reading many positive reviews, I decided to have a go at running Rspamd instead of setting up SpamAssassin again. I was also able to do away with Policyd-SPF/OpenDKIM/OpenDMARC, as Rspamd handles all these functions as well!

Like pretty much every other antispam suite, Rspamd needs quite a bit of configuration – here are my notes for a minimally configured setup that seems to do a decent job. For larger servers with a higher email volume, further tuning will almost certainly be required – but this should get you going.

Assumptions

Here’s what you should already have:

  • A Debian Stretch server with Internet access
    (this guide will probably also work on Debian Jessie and versions of Ubuntu, but I haven’t tested it)
  • A functioning LAMP stack
    (note that I’m using Apache rather than nginx)
  • Postfix and Dovecot already configured and delivering mail
    (I recommend Christoph Hass’ ISPMail tutorials – just skip the Spamassassin instructions)
  • Ability to update DNS records for your domain(s)
  • Knowledge of how and when to use sudo

Overview

Here’s what you need to do:

Install Rspamd and Redis

Note: if you’re following the ISPMail guide (or other similar guide) for Postfix and Dovecot, don’t install spamassassin or spamass-milter!

The packages for Rspamd in Stretch are out of date and unsupported – add the official Rspamd repository before installing Rspamd and Redis.

First, create /etc/apt/sources.list.d/rspamd.list and add the following line (you’ll need to change the release code name if you’re not using Stretch):

deb http://rspamd.com/apt-stable/ stretch main

Next, get the GPG key and install stuff:

wget https://rspamd.com/apt-stable/gpg.key
apt-key add gpg.key
apt-get update
apt-get install rspamd redis-server

Create config files for Rspamd and Redis

First, create a secure password for the Rspamd controller using rspamadm – you’ll need the output from this command in one of the config files:

rspamadm pw

Next, create a bunch of config files – these can be adjusted to taste, but the aim here is a minimal config to get things up and running – you can check the official documentation for additional options.

Set host and port for the main Rspamd worker:
/etc/rspamd/local.d/worker-normal.inc

bind_socket = "localhost:11333";

Configure the Rspamd proxy to talk to Postfix via a milter:
/etc/rspamd/local.d/worker-proxy.inc

bind_socket = "localhost:11332";
milter = yes;
timeout = 120s;
upstream "local" {
  default = yes;
  self_scan = yes;
}

Configure a socket for the Rspamd controller (used for the web interface and for Dovecot sieve filters). This is where you use the password generated by rspamadm earlier – you may want to use separate standard and “enable” (superuser) passwords:
/etc/rspamd/local.d/worker-controller.inc

password = "$2$s164jny.....";
enable_password = "$2$s164jny.....";
bind_socket = "localhost:11334";

Make the Rspamd bayes classifier use Redis:
/etc/rspamd/local.d/classifier-bayes.conf

servers = "127.0.0.1";
backend = "redis";
autolearn = true;

Choose which headers get added to emails. This is a matter of personal taste, but I like the following as it’s not too verbose:
/etc/rspamd/local.d/milter_headers.conf

use = ["authentication-results", "x-spam-status"];
authenticated_headers = ["authentication-results"];

If a user has replied to an email, don’t mark other emails in the same thread as spam:
/etc/rspamd/local.d/replies.conf

action = "no action";

Add URL redirect checks to the URL blacklist check – note that hitting some of these blacklist services more frequently might require purchasing a license:
/etc/rspamd/local.d/surbl.conf

redirector_hosts_map = "/etc/rspamd/redirectors.inc";

Assign dynamic reputation to certain TLDs:
/etc/rspamd/local.d/url_reputation.conf

enabled = true;

Cache URL tags in Redis:
/etc/rspamd/local.d/url_tags.conf

enabled = true;

Where to find Redis:
/etc/rspamd/local.d/redis.conf

servers = "127.0.0.1";

Now we know where to find Redis, adjust Redis’ configuration so it binds to the appropriate IP addresses and is a little less memory hungry – /etc/redis/redis.conf already exists, you just need to adjust one line and add two others:

bind 127.0.0.1 ::1
maxmemory 500mb
maxmemory-policy volatile-lru

A couple other configuration items to consider –

Configure a local DNS resolver – not really necessary for low volumes of mail, as Rspamd will use whatever is in /etc/resolv.conf – however for higher volumes of mail, install something like Unbound and then:
/etc/rspamd/local.d/options.inc

dns {
  nameserver = ["127.0.0.1:53:1"];
}

Check for phishing emails. Phishing checks chew up an insane amount of memory (~800 MB for the few minutes I had it switched on), so you may not want this..:
/etc/rspamd/local.d/phishing.conf

openphish_enabled = true;
phishtank_enabled = true;

Get Postfix talking to Rspamd

We’ve already set up Rspamd to talk to Postfix via the milter protocol – now we need to set up Postfix to join the conversation. Update /etc/postfix/main.cf – if you already have a milter section, it will need to be adjusted:

milter_protocol = 6
milter_default_action = accept
smtpd_milters = inet:localhost:11332
non_smtpd_milters = $smtpd_milters
milter_mail_macros =  i {mail_addr} {client_addr} {client_name} {auth_authen}

Now that Rspamd and Postfix are talking to each other, restart both services. New inbound email should have headers like “X-Spam-Status:” added by Rspamd.

systemctl restart postfix
systemctl restart rspamd

Get Dovecot’s sieve and imapsieve plugins to train Rspamd

If Rspamd gets spam detection wrong for a given email, users can retrain Rspamd themselves by moving the email either out of the Junk folder or into the Junk folder as appropriate.

First, enable a couple sieve plugins within the protocol imap { } and protocol lmtp { } sections:

/etc/dovecot/conf.d/20-imap.conf

protocol imap {
  mail_plugins = $mail_plugins imap_sieve
}

/etc/dovecot/conf.d/20-lmtp.conf

protocol lmtp {
  mail_plugins = $mail_plugins sieve
}

Next, create a directory for the sieve filters:

mkdir /etc/dovecot/sieve

Tell Dovecot to pay attention to emails being shifted between folders and to trigger the sieve filters:

/etc/dovecot/conf.d/90-imapsieve.conf

plugin {
  sieve_plugins = sieve_imapsieve sieve_extprograms

  # From elsewhere to Junk folder
  imapsieve_mailbox1_name = Junk
  imapsieve_mailbox1_causes = COPY
  imapsieve_mailbox1_before = file:/etc/dovecot/sieve/report-spam.sieve

  # From Junk folder to elsewhere
  imapsieve_mailbox2_name = *
  imapsieve_mailbox2_from = Junk
  imapsieve_mailbox2_causes = COPY
  imapsieve_mailbox2_before = file:/etc/dovecot/sieve/report-ham.sieve

  sieve_pipe_bin_dir = /etc/dovecot/sieve

  sieve_global_extensions = +vnd.dovecot.pipe
}

Create the relevant sieve filters:

/etc/dovecot/sieve/report-spam.sieve

require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];

if environment :matches "imap.email" "*" {
  set "email" "${1}";
}

pipe :copy "learn-spam.sh" [ "${email}" ];

/etc/dovecot/sieve/report-ham.sieve

require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];

if environment :matches "imap.mailbox" "*" {
  set "mailbox" "${1}";
}

if string "${mailbox}" "Trash" {
  stop;
}

if environment :matches "imap.user" "*" {
  set "username" "${1}";
}

pipe :copy "learn-ham.sh" [ "${email}" ];

Restart Dovecot, then compile the two sieve filters:

systemctl restart dovecot
sievec /etc/dovecot/sieve/report-spam.sieve
sievec /etc/dovecot/sieve/report-ham.sieve

Finally, add the scripts referenced in the sieve filters that tell Rspamd to learn as spam or learn as ham:

/etc/dovecot/sieve/learn-spam.sh

exec /usr/bin/rspamc -h localhost:11334 learn_spam

/etc/dovecot/sieve/learn-ham.sh

exec /usr/bin/rspamc -h localhost:11334 learn_ham

Make the scripts executable, then restart Dovecot one more time:

chmod 755 /etc/dovecot/sieve/learn-spam.sh
chmod 755 /etc/dovecot/sieve/learn-ham.sh
systemctl restart dovecot

Create SPF records for your domain

Configuring outbound SPF involves working out what mail servers you could potentially send through and then crafting an appropriate DNS TXT record (note that DNS SPF records have been deprecated and shouldn’t be used).

There are plenty of decent wizards and tutorials out there, but I like the easySPF wizard. Because I run my own mail server and never, ever send through any other mail service, my record looks like this:

example.com. IN TXT "v=spf1 mx -all"

If I also wanted to use Gmail or Google Apps to send email from my domain, I’d need to change it to look like this:

example.com. IN TXT "v=spf1 mx include:_spf.google.com -all"

Configure DKIM signing in Rspamd

DKIM won’t improve spam detection rates, so this section can be skipped – but valid DKIM signatures are often used by antispam systems as a negative score, so it may improve delivery rates for your outbound email.

Thankfully, DKIM signing in Rspamd is even easier than setting up OpenDKIM! Create a directory for DKIM keys and then generate a key – I’ve used the domain name in the file name (this makes it easier to have different keys for different domains), and I’m using “dkim” as a selector (use something like the server name for multi-server configs) – adjust the file name in the command below to taste:

sudo mkdir /var/lib/rspamd/dkim
sudo rspamadm dkim_keygen -k /var/lib/rspamd/dkim/example.com.dkim.key -b 2048 -s dkim -d example.com

The rspamadm dkim_keygen command above will create the private key file and display the public key for the DNS TXT record on screen – the public key isn’t saved anywhere, so add the DNS TXT record immediately as follows:

  • The name of the record should be the selector, followed by _domainkey, followed by the domain
    (so dkim._domainkey.example.com for this example).
  • The value for the record needs to be copied carefully – the three lines should end up on a single line in the record, with spaces in between each:
dkim._domainkey.example.com. IN TXT "v=DKIM1; k=rsa;" "p=MIIBI.....HtByA" "504pO.....DAQAB"

Protect the private key:

chown -R _rspamd._rspamd /var/lib/rspamd/dkim
chmod 640 /var/lib/rspamd/dkim/example.com.dkim.key

Enable DKIM signing and ARC signing in Rspamd with two new config files – both files have the same content, but they both need to exist. Note that both the domain and selector are called out – this is to allow for multiple domains and different keys. Additionally, allow username mismatches when signing:
/etc/rspamd/local.d/dkim_signing.conf
/etc/rspamd/local.d/arc.conf

path = "/var/lib/rspamd/dkim/$domain.$selector.key";
selector = "dkim";
allow_username_mismatch = true;

Restart Rspamd:

systemctl restart rspamd

It’s extremely important to test the SPF and DKIM setup at this point – don’t just assume everything is working. Incorrect SPF/DKIM configs will break email delivery.

First, wait for your DNS updates to propagate – then check the SPF record and the DKIM record to make sure they are valid.

If these checks pass, use DKIMValidator.com to ensure that outbound mail is being correctly signed.

Configure Apache to reverse proxy Rspamd’s web interface

If you’ve already got admin websites up and running, add access to the shiny Rspamd web interface by using an Apache reverse proxy – first, enable two Apache modules:

a2enmod proxy
a2enmod proxy_http

Add the following to an existing virtual host – the Rspamd web interface will be available at https://example.com/rspamd/ (note the trailing slash):

RewriteEngine On
RewriteRule ^/rspamd/(.*) http://127.0.0.1:11334/$1 [P,L]
<Location /rspamd>
  Options FollowSymLinks
  Require all granted
</Location>

Restart Apache:

systemctl restart apache2

Train Rspamd with existing ham/spam corpora

Rspamd works without training – but training will definitely improve accuracy! For my training, I used the contents of my Inbox as a source of ham and the contents of my Junk folder as a source of spam – modify the following to suit your environment:

rspamc learn_ham /var/vmail/example.com/user/cur/
rspamc learn_spam /var/vmail/example.com/user/.Junk/cur/

You will probably get a few errors during the training process – some of the more common ones include:

  • <MessageID> contains less tokens than required for bayes classifier
    (the email was too short to classify)
  • <MessageID> has been already learned as ham, ignore it
    (means exactly what it says – the email is similar enough to one already in the database)

Acquire spam for additional training

If you’re like me, you have lots of ham for training purposes – but you’ve deleted most of your spam. It’s important to train both, so you really need some recent spam to tell Rspamd what to look for (old spam isn’t as useful, as spam changes over time).

Art Invoice make their spam corpora available to download – each day has a gzipped file filled with fresh spam ready to train with! Just one problem – it’s in mbox format, rather than Maildir format.

I found a python script to convert mbox files to Maildirs on GitHub – grab that, call it mb2md.py, make it executable and then put it in the same folder as this shell script (which needs to be executable as well):

/path/to/getspam.sh

#!/bin/bash
spamfile=spam--`date '+%Y-%m-%d'`.gz
spamfile_unpacked=spam--`date '+%Y-%m-%d'`
wget http://artinvoice.hu/spams/$spamfile
gunzip $spamfile
./mb2md.py -i $spamfile_unpacked -o spam/
rspamc learn_spam spam/cur/
rm -r $spamfile_unpacked spam/
exit 0

Run it once (as root) to test, then add it to cron for automated daily spam training. Rspamd will already detect most of the messages as spam – you’ll see errors like this:

HTTP error: 410, <MessageID> is skipped for bayes classifier: already in class spam; probability 100.00%

..but there will be enough that get learned to make this a worthwhile exercise.

Filter everyone’s spam into the Junk folder

This step is definitely optional and I’d recommend holding off on this until you’re comfortable that Rspamd is doing a good job of detecting spam.

Out of the box, Rspamd has a few too many false positives for my liking – so I decided to raise the score at which the X-Spam: Yes header gets added from 6 to 8. You can modify this setting with the Rspamd web interface, in the Configuration tab (it’s the setting called “Probably Spam”). I’d recommend adjusting this before creating the following global sieve filter..

First, edit /etc/dovecot/conf.d/90-sieve.conf and add the following line:

sieve_after = /etc/dovecot/sieve/after.d/

Create the directory mentioned above:

mkdir /etc/dovecot/sieve/after.d/

Create the sieve filter – /etc/dovecot/sieve/after.d/junk.sieve

require ["fileinto","mailbox"];
if header :contains "X-Spam" "Yes" {
 fileinto :create "Junk";
 stop;
}

Finally, compile the sieve filter, update privileges and restart Dovecot:

sievec /etc/dovecot/sieve/after.d/junk.sieve
chown -R vmail.vmail /etc/dovecot/sieve/
systemctl restart dovecot

References and notes

I couldn’t have written this guide without extensive Googling – among other sites, these were the most helpful:

I will probably tweak this guide as I learn more about Rspamd – I will note any updates I make here.

  • 2017-10-15: Added notes on how to acquire additional spam for training purposes
  • 2017-10-17: Included instructions for enabling the Dovecot imap_sieve and sieve plugins
  • 2017-10-17: Simplified worker controller configuration to use a single bind_socket
  • 2018-01-28: Updated Apache proxy configuration to remove redundant config option
  • 2018-06-05: Change privileges on the Dovecot sieve script directory

Any feedback is greatly appreciated – I hope you find this guide useful!

SpamAssassin sa-learn cron script for virtual users

Because I couldn’t find something to fully automate the SpamAssassin sa-learn process for virtual email users in a MySQL database, I wrote my own (borrowing liberally from Jason Schaefer’s SpamAssassin training and spam cleanup script).

This script assumes that:

  • You have a MySQL database with virtual users, with a user table called ‘virtual_users’ and the full email address stored in a field called ’email’.
  • Your email is stored in Maildir folders, with a heirarchy starting from /var/vmail/domain.com/username/…
#!/bin/bash

## Database details

USER=''
PASS=''
HOST=''
DB=''

## Where to log stuff

LOG='/var/log/sa-learn.log'

## How many days to wait before deleting spam
## Comment out to disable

CLEAN=30

echo -e "\n\nRun started `date +%c`"  >> $LOG 2>&1

## Spam and ham training for all virtual users
## Delete spam older than $CLEAN days

mysql --skip-column-names -u$USER -p$PASS -h$HOST -D$DB -e "SELECT SUBSTRING(email, 1, LOCATE('@', email) - 1) AS user, SUBSTRING(email, LOCATE('@', email) + 1) AS domain FROM virtual_users" | while read user domain;

do
  ## Spam
  echo "Spam training for $user@$domain" >> $LOG 2>&1
  /usr/bin/sa-learn --no-sync --spam /var/vmail/$domain/$user/.Junk/{cur,new} >> $LOG 2>&1
  ## Ham
  echo "Ham training for $user@$domain" >> $LOG 2>&1
  /usr/bin/sa-learn --no-sync --ham /var/vmail/$domain/$user/{cur} >> $LOG 2>&1
  ## Delete
  if [ -n $CLEAN ]; then
    echo "Deleting spam for $user@$domain older than $CLEAN days" >> $LOG 2>&1
    find /var/vmail/$domain/$user/.Junk/cur/ -type f -mtime +$CLEAN -exec rm {} \;
  fi
done

## Sync the SpamAssassin journal and print out stats

echo "Syncing the SpamAssassin journal" >> $LOG 2>&1
/usr/bin/sa-learn --sync >> $LOG 2>&1
echo "Statistics for this run:" >> $LOG 2>&1
/usr/bin/sa-learn --dump magic >> $LOG 2>&1

echo -e "Run finished `date +%c`"  >> $LOG 2>&1

exit

Postfix with SPF, DKIM and DMARC

Using the tutorials at workaround.org for setting up Postfix with virtual users and domains is generally excellent, but the tutorial for Debian Jessie (with Postfix 2.11) doesn’t contain instructions for setting up authenticated mail delivery (SPF, DKIM and DMARC). Here are my notes:

Pre-requisites

  • An otherwise-functioning email setup (these notes build on the workaround.org tutorial)
  • Understanding of (and ability to modify) DNS records

Inbound SPF

SPF has its pros and cons, but on the whole it’s pretty useful. I’d recommend configuring inbound SPF checks to only reject mail that has a “hard fail” for SPF (vs. “soft fail” or “neutral”).

Install the following:

sudo aptitude install postfix-policyd-spf-python

In /etc/postfix/main.cf, in the section shown below – add the check_policy_service line before any RBL checks:

smtpd_recipient_restrictions =
  ...
  check_policy_service unix:private/policy-spf
  ...

Other SPF tutorials recommend extending the SPF time limit to 3600 seconds (from a default of 1000 seconds) – assuming your server is on a decently quick connection, I can’t see a good reason to change the default.

In /etc/postfix/master.cf, enable the SPF service as follows:

policy-spf unix -       n       n       -       -       spawn
  user=nobody argv=/usr/bin/policyd-spf

In /etc/postfix-policyd-spf-python/policyd-spf.conf, ensure the configuration is set to only reject messages that “hard fail” the SPF check – remove the existing HELO_reject line and replace as follows:

HELO_reject = Fail

Restart postfix:

sudo systemctl restart postfix

Send yourself a test email (from Gmail or similar) to ensure you’re getting lines like this in your mail.log:

policyd-spf[22747]: None; identity=helo; client-ip=2607:f8b0:4003:c06::230; helo=mail-oi0-x230.google.com; envelope-from=user@gmail.com; receiver=user@example.com
policyd-spf[22747]: Pass; identity=mailfrom; client-ip=2607:f8b0:4003:c06::230; helo=mail-oi0-x230.google.com; envelope-from=user@gmail.com; receiver=user@example.com

Outbound SPF

Configuring outbound SPF involves working out what mail servers you could potentially send through and then crafting an appropriate DNS TXT record (note that DNS SPF records have been deprecated and shouldn’t be used).

There are plenty of decent wizards and tutorials out there, but I like the easySPF wizard. Because I run my own mail server and never, ever send through any other mail service, my record looks like this:

example.com. IN TXT "v=spf1 mx -all"

If I also wanted to use Gmail or Google Apps to send email from my domain, I’d need to change it to look like this:

example.com. IN TXT "v=spf1 mx include:_spf.google.com -all"

You can absolutely break mail delivery for your domain using SPF, so it’s really important to understand what you’re putting in to your DNS records.

DKIM

DKIM is much, much easier than it used to be – and is becoming more and more important in the fight against spam. For a more comprehensive introduction, Eric Allman from Sendmail put together a nice overview of DKIM.

Different tutorials use different packages – I install the following:

sudo aptitude install opendkim opendkim-tools libmail-dkim-perl libcrypt-openssl-random-perl libcrypt-openssl-rsa-perl

The first two packages install OpenDKIM; the remaining packages are so SpamAssassin can handle DKIM signed messages.

Make sure the following directories exist (create them if they don’t):

sudo mkdir /etc/opendkim
sudo mkdir /etc/opendkim/keys

My /etc/opendkim.conf file is as follows. You’ll note that it differs from other tutorials in two main ways:

  • I’m using virtual domains, so I’m not putting everything in the main configuration file
  • I’m not using PID files to communicate with Postfix, so I don’t have things like UMask and PidFile set
AutoRestart             yes
AutoRestartRate         10/1h

Syslog                  yes
SyslogSuccess           yes
LogWhy                  yes

Canonicalization        relaxed/simple

ExternalIgnoreList      refile:/etc/opendkim/TrustedHosts
InternalHosts           refile:/etc/opendkim/TrustedHosts
KeyTable                refile:/etc/opendkim/KeyTable
SigningTable            refile:/etc/opendkim/SigningTable

Mode                    sv
SignatureAlgorithm      rsa-sha256

UserID                  opendkim:opendkim

Socket                  inet:12345@localhost

OversignHeaders         From

Note the Socket line above – that needs to be configured elsewhere as well.

In /etc/default/opendkim, comment out everything except the following:

SOCKET="inet:12345@localhost" # listen on loopback on port 12345

In /etc/postfix/main.cf:

milter_protocol = 6
milter_default_action = accept
smtpd_milters = inet:localhost:12345
non_smtpd_milters = $smtpd_milters

If you’ve followed the workaround.org tutorial, you will already have smtpd_milters = unix:/spamass/spamass.sock – if so, the smtpd_milters line above should be changed to:

smtpd_milters = unix:/spamass/spamass.sock, inet:localhost:12345

Create the TrustedHosts, KeyTable and SigningTable files as follows (substitute your domain(s) for example.com in all of them)

/etc/opendkim/TrustedHosts:

127.0.0.1
::1
localhost

8.8.8.8
2001:4860:4860::8888/64

*.example.com

Substitute the second block of IP addresses with real addresses from your server – don’t leave the Google DNS addresses there! Note that the /64 at the end of the IPV6 address is required.

/etc/opendkim/KeyTable

Note multiple domain substitutions; DKIM also uses ‘selectors’ which are defined here. I’ve used “mail” as the selector (some folk will recommend something like “jan2016”), but you can use whatever you’d like:

mail._domainkey.example.com example.com:mail:/etc/opendkim/keys/example.com/mail.private

/etc/opendkim/SigningTable (again with the multiple domain substitutions):

*@example.com mail._domainkey.example.com

You’ll notice that the KeyTable file mentions a directory for your domain – create that now:

sudo mkdir /etc/opendkim/keys/example.com

In the directory you just created, create the private/public key pair (note another domain substitution, and if you changed the selector above from “mail” to something else, you’ll need to change it here too):

sudo opendkim-genkey -b 2048 -s mail -d example.com

Change the owner of the private key:

sudo chown opendkim:opendkim mail.private

The mail.txt file contains the TXT record you need to add to your DNS. Note that the key is broken up in to multiple quoted sections – be careful when adding it to your DNS records to make sure it’s all added correctly.

Wait until the DNS change has propagated, then confirm that the DKIM key in your DNS records passes a validation check. If everything looks good, restart the opendkim and postfix processes:

sudo systemctl restart opendkim
sudo systemctl restart postfix

Send a test email to a Gmail account. Here’s what you should see in the mail logs for the outbound message:

opendkim[11862]: D11F114939: DKIM-Signature field added (s=mail, d=example.com)

In Gmail, when the test email arrives, it should have the following headers:

...
Authentication-Results: mx.google.com;
       spf=pass (google.com: domain of user@example.com designates [IP Address] as permitted sender) smtp.mailfrom=user@example.com;
       dkim=pass header.i=@example.com;
...
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=example.com; s=mail;
...

Reply back to the email and check the mail logs for the following:

opendkim[11862]: 0EBC014939: mail-ob0-x235.google.com [2607:f8b0:4003:c01::235] not internal
opendkim[11862]: 0EBC014939: not authenticated
opendkim[11862]: 0EBC014939: DKIM verification successful
opendkim[11862]: 0EBC014939: s=20120113 d=gmail.com SSL

If you have SpamAssassin installed, you should also see something like the following:

spamd[18803]: spamd: result: . 0 - DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FROM,HTML_MESSAGE,SPF_PASS,UNPARSEABLE_RELAY scantime=0.4,size=2698,user=user,uid=5000,required_score=5.0,rhost=::1,raddr=::1,rport=52616,mid=<CAEbJLUEKOOGQabWPgjwXzW-_Y0PjUFH1+jo7cz50X3BOxYNf7w@mail.gmail.com>,autolearn=ham autolearn_force=no

Another test you can (and should) run is the Newsletters spam test, as it will make sure your mail is correctly signed using DKIM.

Once you are absolutely certain that both SPF and DKIM are working correctly…

Outbound DMARC

Similar to outbound SPF, outbound DMARC is set up as a DNS TXT record that defines how receiving mail servers should handle messages that fail SPF and DKIM checks. DMARC is only useful if SPF and DKIM are working for your domain, AND if all outbound mail for your domain goes through your servers.

If other servers are used (e.g., using an ISP mail server as a smarthost), then do not configure a DMARC record!

I really like Google’s guide for Preventing outgoing spam with DMARC and consider it mandatory reading.

Google’s guide will help you configure your own DMARC record, but as an example – here’s mine:

_dmarc.example.com. IN TXT "v=DMARC1; p=quarantine; pct=100; rua=mailto:postmaster@example.com"

Inbound DMARC

Install the following:

sudo aptitude install opendmarc

My /etc/opendmarc.conf – I’ve chosen to not send forensic/aggregate reports and to not reject mail that fails DMARC (for the time being):

AutoRestart             Yes
AutoRestartRate         10/1h

UserID                  opendmarc:opendmarc
Socket                  inet:54321@localhost
Syslog                  true
SyslogFacility          mail

AuthservID              mail.server.fqdn
#TrustedAuthservIDs      other.mail.server, another.mail.server
IgnoreHosts             /etc/opendkim/TrustedHosts

RejectFailures          false

Uncomment the TrustedAuthservIDs if you have additional mail server(s) configured for the same domain(s) that also have OpenDMARC installed.

Next, configure the socket by adding the following line to /etc/default/opendmarc (don’t uncomment anything):

SOCKET="inet:54321@localhost"

Add to the list of milters in /etc/postfix/main.cf:

smtpd_milters = unix:/spamass/spamass.sock, inet:localhost:12345, inet:localhost:54321

Start and restart the appropriate processes:

sudo systemctl start opendmarc
sudo systemctl restart postfix

Send yet another test email from Gmail and then look for the following in the mail logs:

opendmarc[23774]: E24C514939: gmail.com pass

You’ll also see the following in the mail header:

Authentication-Results: mail.server.fqdn; dmarc=pass header.from=gmail.com