More than two years have passed since my first post about setting up an email server in 2020 with OpenSMTPD and Dovecot and its sequel. Since then, my server been going strong, with a few minor hiccups along the way. In this post, I’ll explain some of the changes I made.
This assumes you’ve followed the preceding guides: the configuration snippets given here should be interpreted as modifications to those guides, not as complete setups!
Last updated on 2022-09-12.
When I wrote my original guide, I didn’t properly understand how DMARC works: I misinterpreted it as an optional wrapper around SPF and DKIM. But oh God, I was wrong. Simon Andrews’ article “I figured out how DMARC works, and it almost broke me” showed me the ugly truth, and I highly recommend reading it. Briefly, DMARC does two arguably unrelated things.
Firstly, DMARC provides a way to diagnose issues with your SPF and DKIM configurations,
in the form of reports that get sent to the
rua= email address(es)
you put in the DNS record.
Without this, there’s no way of knowing why
your emails are getting marked as spam.
Secondly, it improves the trustworthiness of SPF and DKIM by enforcing alignment. This means something slightly different for SPF and DKIM, and boils down to fixing a glaring issue:
From:header doesn’t need to agree with the address in the SMTP
MAIL FROMcommand; in other words, the server can claim a different sender than what’s written in the message’s header. SPF only verifies the former (i.e. it takes the domain in
MAIL FROM), so one SMTP server can impersonate another.
d=tag, but, once again, that doesn’t need to agree with the
From:header’s domain. DKIM doesn’t look at the latter, so an SMTP server can validly sign impersonated messages.
DMARC’s alignment refers to checking whether the domains match up for SPF and DKIM, thus ensuring that an SMTP server can’t pretend to be someone else. It sounds obvious, but nope, apparently it wasn’t before DMARC was made.
To add DKIM signatures to my messages, I switched from Rspamd to DKIMproxy.
To sign outgoing emails for DKIM, my original guide used Rspamd — an unusual choice, since it’s a spam filter designed to act on incoming messages. Later, in the sequel to that guide, I needed some ugly workarounds to compensate for Rspamd’s “smartness” when I tried to play around with authentication schemes in OpenSMTPD. Clearly, this wasn’t ideal.
However, the reason I cut my losses and switched to another DKIM signer was actually a bug in MXToolBox’ deliverability tool. It appears that no matter what you do, this tool claims that your email’s signature fails validation. I’m not the first to notice this issue: see e.g. this question on Server Fault. Other tools like the DKIM validator say that my DKIM signatures are correct.
There aren’t many open-source alternatives out there for DKIM signing:
the only ones I know of are OpenDKIM
The former is a so-called “milter”,
meaning it can only interact with MTAs via the milter API,
which is only supported by Sendmail
Since we’re using OpenSMTPD,
our only option is DKIMproxy,
which consists of two daemons:
dkimproxy.out to sign outgoing mail,
dkimproxy.in to verify incoming mail.
We just need the former;
Rspamd is still convenient for handling the latter’s functionality.
Let’s start by disabling Rspamd’s DKIM signer
enabled = false;
dkimproxy.out as follows
If you placed your DKIM public key
in a TXT DNS record for
and stored your private key in
# Receive emails on 10027, sign them, and forward them to 10028 listen 127.0.0.1:10027 relay 127.0.0.1:10028 # Settings for email signing domain example.com signature dkim(c=relaxed/relaxed,a=rsa-sha256) keyfile /path/to/dkim/private.key selector <selector>
rsa-sha256 is the signature algorithm
(this is the best available, because DKIM is ancient),
relaxed/relaxed is the so-called canonicalization method,
which is applied before signing and verification,
to prevent failures if e.g. the email’s whitespace gets changed in transit.
OpenSMTPD needs to send all outbound mail through
/etc/smtpd/smtpd.conf, we tell it that all emails coming from the MUA
must be relayed through
and then picked up again on
localhost:10028 after DKIM signing:
# Outbound listen on eth0 port 465 smtps pki "example.com" auth <passwds> tag "TRUSTED" listen on eth0 port 587 tls-require pki "example.com" auth <passwds> tag "TRUSTED" action "SIGN" relay host "localhost:10027" match from any tag "TRUSTED" for any action "SIGN" listen on lo port 10028 tag "SIGNED" action "SEND" relay srs match from any tag "SIGNED" for any action "SEND"
The tag name
TRUSTED reflects that only messages from trusted
(i.e. authenticated) MUAs should be signed.
After signing, emails get the tag
and are sent to their destination as usual.
Instead of sending my emails directly to their destinations, I now send them to an SMTP relay server, which then passes them on to their actual destinations.
Large email providers such as Google, Microsoft and Yahoo manage many user accounts, so for them it makes sense to keep track of IP-based sender reputations. For example, if a number of low-quality emails are sent from a single IP to many of the accounts they manage, it’s cheaper to simply blacklist that IP entirely at the MTA level, rather than passing each message through a computationally-intensive spam filter.
But, as usual, Microsoft has to ruin everything with their draconic policies. In a stroke of genius, someone there decided to blindly ban IPs, seemingly in blocks belonging to VPS providers. One day, I tried to send an email to an Outlook-based account, and OpenSMTPD reported it had been unable to make the delivery, because Microsoft had thrown an error:
To their credit, they seem to be offering a way out. This approach is reasonable: preventively ban high-risk IP ranges, and allow “trustworthy” servers at the owner’s request. I got error 5.7.511, asking me to send an email to a support address. If you’re lucky, you may have a different error, and get the opportunity to use the slick delist portal instead. The URL in the bounce message links to this list of error codes.
I confess, I never actually bothered to forward the message to the provided address: my initial email was time-sensitive, so I couldn’t afford to wait for Microsoft’s response. Also, their customer support’s stellar reputation precedes them, so I chose to use my time more wisely. Even if they would’ve resolved it nicely, there’s nothing preventing Microsoft (or any other provider) from breaking my deliverability again in the future. Instead, I opted for a compromise.
As a result of providers’ IP reputation systems, a whole new business has appeared: SMTP relays. They offer to take the issue out of your hands: you send your emails through their servers, and they do their best to deliver them to large providers. SMTP relays are mostly used for sending marketing emails in bulk, but are also useful to avoid small-scale problems as described above. There are many SMTP relay services to choose from, at various prices.
Using an SMTP relay results in more reliable delivery of your messages to large providers, but an obvious concern is privacy: the relay server can read all your outgoing emails (but not incoming), so you’ll have to trust the service you choose. But it’s no worse than using a major provider, and if you’re sending sensitive material, why use email in the first place? Personally, I use SMTP2GO, but I can’t say how good they are; do your own research.
Once you’ve chosen an SMTP relay service, let’s say
and set up your account,
they’ll let you create credentials to use their SMTP servers.
Suppose these credentials are
create a file
/etc/smtpd/relaypw with contents:
<label> is a string of your choice.
<password> must be plaintext,
because it needs to be provided to the relay server.
To tell OpenSMTPD to use the relay,
/etc/smtpd/smtpd.conf as follows,
i.e. register the
relaypw table and modify the
table relaypw "/etc/smtpd/relaypw" #action "SEND" relay srs ### Replace this line with the following: action "SEND" relay host "smtps://<label>@relay.com:465" auth <relaypw> pki "example.com" srs
<label> replaced by the label you chose earlier,
relay.com:465 replaced by the host/port combination
given in the relay service’s documentation.
Depending on what they support, you may also need to change the protocol
smtps:// (SMTP over TLS)
smtp:// (SMTP with optional STARTTLS) or
smtp+tls:// (SMTP with mandatory STARTTLS).
I recommend SMTPS.
If you’ve been paying attention so far, you have a burning question: what about SPF and stuff? Wasn’t the point to prevent SMTP servers from sending emails on others’ behalf? Well, yes, so you’ll need to add some DNS records for the relay to work. The details depend on which service you choose, so they’ll tell you what to do when you’re setting up your account. As an example, based on my experience with SMTP2GO, you may need to add two CNAME records like:
emXXXXXX.example.com. CNAME return.relay.com. sXXXXXX._domainkey.example.com. CNAME dkim.relay.com.
Which, roughly speaking, respectively enable SPF and DKIM.
XXXXXX is an ID that the service will provide to you,
since the DNS addresses must be unique.
Technically, those two DNS records should be enough for SPF and DKIM, but in practice, it seems that different email providers/tools have slightly different interpretations of these standards, and can get confused when an email passes through multiple unaffiliated SMTP servers. Therefore, I recommend explicitly adding the relay to your SPF policy:
example.com TXT "v=spf1 mx include:spf.relay.com -all"
Your relay service might not publicly document their version of
but you can find it by looking up your CNAME
emXXXXXX.example.com (or equivalent)
in MXToolBox’ SPF tool.
I also recommend relaxing your DMARC domain policy for SPF and DKIM, such that your CNAME subdomains still pass the alignment checks:
_dmarc.example.com. TXT "v=DMARC1; aspf=r; adkim=r; ..."
Be sure to check that it’s set up correctly using the website “Learn and test DMARC”.