Skip to content

Mail Server with Postfix and Dovecot#

This post explains how to setup a Postfix SMTP server and a Dovecot IMAP server for personal use. For enterprise I recommend selecting a trusted vendor.

This post will go through:

  • the basics of email
  • the configuration for Postfix
  • the configuration for Dovecot and the auth
  • the security (encryption)
  • the DNS management: focus on being trustful (a.k.a not being seen as spam)

TL;DR "I just want to copypaste the config and have a mail server now"

Well, sadly there's no valid TL;DR here :(

Setting up a mail server requires time. This is a long post that has to be tackled with patience.

If you don't have enough time then this is your best choice (and normally it's always the best choice)

Requirements#

You will need:

  • a host with a public IP and a Debian-based distro installed, Ubuntu Focal used for this post
  • a domain name pointing to this IP
  • access to configure the DNS zones of that domain

Understanding the basics#

We all know the concept about sending an email and receiving it, so here we have two actions. For both actions the "email" world has established protocols that the applications can follow. For sending we have the SMTP protocol thanks to the ARPANET in 1983 and for receiving we have IMAP and POP3. For the sake of living in the present and keeping this documentation simple we can just focus on SMTP (send) and IMAP (receive).

The title of this post includes two buzzwords "Postfix" and "Dovecot", we can map them:

  • Postfix -> SMTP
  • Dovecot -> IMAP

So we have a Postfix to send email and a Dovecot to receive email (well, not exactly)

Let's work a bit more on the previous affirmation, and for that let's show a simple structure of clients and servers sending an email.

Imagine that Bob wants to send an email to Alice. Bob has his own client and his own mail server, and Alice the same, something like this:

- [Bob client]      -> speaks with -> [Bob Server]
- [Alice client]    -> speaks with -> [Alice Server]

Imagine we have [Bob server] working under @bob.com and [Alice Server] under @alice.com. How does an email travel from Bob to Alice? First the client sends an email to its own SMTP server and then the SMTP server checks the @destination.com and sends it to the IP of that @destination.com, in this case @alice.com which identifies [Alice Server].

So if Bob sends an email to alice@alice.com this will be the path to follow:

[Bob client] -> [Bob Server][SMTP] -> relay -> [Alice Server][SMTP] -> [Alice client]

Here appears the buzzword relay which basically is the same between client->server but between SMTP Servers.

Ok, nice, and where is IMAP? Do we need it? Maybe not, maybe yes. The truth is that we need it. Once [Bob server][SMTP] has relayed Bob's email to [Alice Server][SMTP], [Alice Server][SMTP] validates the email (spam, etc) and if it's ok then the email is sent to [Alice Server][IMAP]. So the whole traveling would be something like this:

  1. [Bob client]:
    • writes an email with alice@alice.com as recipient
    • sends this email to [Bob server][SMTP]
  2. [Bob server][SMTP]:
    • authenticates [Bob client] (if no auth, no email)
    • relays the email to [Alice Server][SMTP] (it has to find the IP based on the @alice.com)
  3. [Alice Server][SMTP]:
    • validates the email coming from [Bob server][SMTP]
    • sends the email to [Alice Server][IMAP]
  4. [Alice client]:
    • requests new email to [Alice Server][IMAP]
  5. [Alice Server][IMAP]:
    • authenticates [Alice client] (if no auth, no email)
    • provides the email contents to [Alice client]
  6. [Alice client]:

So:

[client] -> [SMTP] -> relay -> [SMTP] -> [IMAP] -> [client]

Now we know the traveling but why we add an IMAP there? Why not just querying from the client directly to the SMTP? Ok, that's a good one. Maybe it's because somebody decided to implement it like this with this protocol, but the concept is:

  • SMTP sends and receives, but it doesn't care about storing
  • IMAP does, he worries about storing

So SMTP for the traveling between mail servers and IMAP to manage all the received emails.

POP3 is like IMAP but older and with less features.

Disclaimer: All the previous summary is very rough top level without going into details, it's a bit more complicated but more or less this is the main idea.

Ok, so now we know a bit the basics and to complete this chapter it's important to mention 3 concepts and keep them in mind:

  • Auth: as we live in an Internet world we need to demonstrate that we are who we are and allow using the service only to ourselves.
  • DNS: the same, to demonstrate that we are who we are, but instead to ourselves, to the world.
  • Encrypting: there are lots of voyeurs out there, we have to keep our content private.

These concepts will be tackled more in deep in their sections, this was just a little spoiler to be prepared. Now let's move on to the practice part.

Setting up Postfix (v3.4.13)#

What's Postfix? This is an easy one, an SMTP server.

How to install it?

v3.4.13 used in this post

$ apt install postfix

and you can ignore all that wizard because we are going to rewrite the config.

What do we want Postfix for? We want to send an email and also to receive it, we need to relay and to be relayed so we need to configure a couple of things.

First of all, postfix has two main config files:

/etc/postfix/main.cf
/etc/postfix/master.cf

The main.cf is for the generic config and the master.cf is to define the services that Postfix will run.

Before going into details, first, let's copy these config files and replace all {REPLACE_*} in main.cf, the master.cf can be copied as it is:

For example in my case:

myhostname = mail.{REPLACE_YOURDOMAIN}
# replace with
myhostname = mail.{REPLACE_YOURDOMAIN}
/etc/postfix/main.cf
# ID
myhostname = mail.{REPLACE_YOURDOMAIN}
myorigin = /etc/mailname
mydestination = $myhostname, {REPLACE_YOURDOMAIN}, localhost.localdomain, localhost

# General
syslog_name=postfix/generic
smtpd_banner = $myhostname ESMTP $mail_name ({REPLACE_YOURDISTRO})
append_dot_mydomain = no
compatibility_level = 2

# Network
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
inet_interfaces = {REPLACE_YOURIP},127.0.0.1
smtp_bind_address = {REPLACE_YOURIP}
inet_protocols = all
smtp_address_preference = ipv4

# TLS
smtpd_tls_cert_file=/etc/letsencrypt/live/mail.{REPLACE_YOURDOMAIN}/fullchain.pem
smtpd_tls_key_file=/etc/letsencrypt/live/mail.{REPLACE_YOURDOMAIN}/privkey.pem
smtpd_tls_security_level = encrypt
smtpd_tls_protocols = >=TLSv1.2
smtpd_tls_loglevel = 1
smtpd_tls_received_header = yes
smtpd_tls_auth_only = yes
smtp_tls_note_starttls_offer = yes
smtp_tls_security_level = encrypt

# Auth
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable=yes

# Mail config
mailbox_size_limit = 0
recipient_delimiter = +
home_mailbox = Maildir/

# Milter
milter_macro_daemon_name=ORIGINATING
milter_default_action = accept
smtpd_milters = inet:localhost:12301
non_smtpd_milters = inet:localhost:12301
/etc/postfix/master.cf
# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (no)    (never) (100)
# ==========================================================================

25      inet  n       -       y       -       -       smtpd -v
587     inet  n       -       -       -       -       smtpd -v

pickup    unix  n       -       y       60      1       pickup
cleanup   unix  n       -       y       -       0       cleanup
qmgr      unix  n       -       n       300     1       qmgr
tlsmgr    unix  -       -       y       1000?   1       tlsmgr
rewrite   unix  -       -       y       -       -       trivial-rewrite
bounce    unix  -       -       y       -       0       bounce
defer     unix  -       -       y       -       0       bounce
trace     unix  -       -       y       -       0       bounce
verify    unix  -       -       y       -       1       verify
flush     unix  n       -       y       1000?   0       flush
proxymap  unix  -       -       n       -       -       proxymap
proxywrite unix -       -       n       -       1       proxymap
smtp      unix  -       -       y       -       -       smtp
relay     unix  -       -       y       -       -       smtp
showq     unix  n       -       y       -       -       showq
error     unix  -       -       y       -       -       error
retry     unix  -       -       y       -       -       error
discard   unix  -       -       y       -       -       discard
local     unix  -       n       n       -       -       local
virtual   unix  -       n       n       -       -       virtual
lmtp      unix  -       -       y       -       -       lmtp
anvil     unix  -       -       y       -       1       anvil
scache    unix  -       -       y       -       1       scache
postlog   unix-dgram n  -       n       -       1       postlogd

maildrop  unix  -       n       n       -       -       pipe
    flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient}
uucp      unix  -       n       n       -       -       pipe
    flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)

ifmail    unix  -       n       n       -       -       pipe
    flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
bsmtp     unix  -       n       n       -       -       pipe
    flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
scalemail-backend unix  -   n   n   -   2   pipe
    flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}
mailman   unix  -       n       n       -       -       pipe
    flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
    ${nexthop} ${user}

Remember to replace REPLACE_YOURDOMAIN, REPLACE_YOURDISTRO

Ok now we have a bunch of config lines that we don't know and they don't even work because they are assuming things that we have not yet configured.

Let's try to understand it first.

Config file main.cf#

Brief description of each parameter:

  • ID:
    • myhostname: This is the FQDN that must have an MX DNS record pointing to this IP
    • myorigin: The contents of /etc/mailname should contain a valid hostname for your mail server. This is used by applications like cron, we can put the same as $myhostname in /etc/mailname.
    • mydestination: This parameter specifies the domains from which we will accept emails to be sent. So if you send a request from a bob@mail.com and mail.com is not in this list, bob will have to find another way to send his email.
  • General:
    • syslog_name: name of the logger in syslog to identify the logs. This can be overridden in each service in master.cf
    • smtpd_banner: Message returned by the server to present himself, it's a convention and if is not respected some servers (like Gmail) can tag you as spam.
    • append_dot_mydomain: prevents sending emails to things like "user@partialdomainname" because the .com won't be automatically added. We prefer to don't modify the domains.
    • compatibility_level: To not go into too many details, less than 2 can accept backwards compatibility but as we are installing this version from scratch we can set all the configuration parameters without backwards compatibility.
  • Network
    • mynetworks: The list of "trusted" remote SMTP clients that are allowed to relay mail through Postfix.
    • inet_interfaces: The local network interface addresses that this mail system receives mail on
    • smtp_bind_address: IP used to send emails. Needed if we have multiple interfaces, and more important if they are public interfaces, because we will setup DNS records to be more trustful that will declare our public IP as the source of the server and it has to match to the IP used by Postfix, if not other SMTP servers can decide to don't trust us.
    • inet_protocols: The Internet protocols Postfix will attempt to use when making or accepting connections, options: ipv4, ipv6 or all which are both.
    • smtp_address_preference: Try to use this protocol before the other, if ipv4 specified Postfix will use ipv4 always as the first option.
  • TLS
    • smtpd_tls_cert_file: TLS cert, we will request one from Letsencrypt with Certbot
    • smtpd_tls_key_file: TLS key, we will request one from Letsencrypt with Certbot
    • smtpd_tls_security_level: Specify encrypt for mandatory TLS encryption for the SMTP server.
    • smtpd_tls_protocols: Declare the accepted protocols for TLS, we only want v1.2 or greater.
    • smtpd_tls_loglevel: Use 2 or higher to debug problems, if not, it can be with 0 as default which is disabled or 1 which is the basic.
    • smtpd_tls_received_header: Add TLS details in the Received field, so when inspecting the original message received from others we can check the TLS details.
    • smtpd_tls_auth_only: When TLS encryption is optional in the Postfix SMTP server, do not announce or accept SASL authentication over unencrypted connections.
    • smtp_tls_note_starttls_offer: Log the hostname of a remote SMTP server that offers STARTTLS, when TLS is not already enabled for that server.
    • smtp_tls_security_level: Use encrypt to force the SMTP client to use TLS.
  • Auth
  • Mail config
    • mailbox_size_limit: Use 0 for unlimited mailbox size, the limit will be the host disk.
    • recipient_delimiter: Postfix allows you to specify a character to separate the user from an additional string. For example with a + as recipient_delimiter we can have the mail user@mail.com and also user+extrastring@mail.com and we will get the mails delivered to user. This is helpful for example when we want to check if a third party who has our address is providing our address to others, for example we provide to randomco.com our address user+rco@mail.com and later we receive an email from spamco.com to user+rco@mail.com, that is pretty clear, randomco.com has not being playing fair.
    • home_mailbox: The default dir in the home of each user which will contain all the mails.
  • Milter (Mail filter)
    • milter_macro_daemon_name: The name of the daemon of the milter that we are going to configure together with OpenDKIM to be more trustful.
    • milter_default_action: We set accept because we don't want to filter in Postfix, we will filter with OpenDKIM.
    • smtpd_milters: The TCP port where our milter will be working, which will be the one of OpenDKIM
    • non_smtpd_milters: The TCP port where our milter will be working, which will be the one of OpenDKIM

This is a good overview of the parameters and the meaning of each one, now let's list the ones that are not currently working because they depend on other actions:

  • myhostname, mydestination: we need a domain name
  • smtpd_tls_cert_file, smtpd_tls_key_file: we need TLS certificates
  • smtpd_sasl_*: we need Dovecot
  • smtpd_milters, non_smtpd_milters: we need OpenDKIM

We will tackle these actions in the next steps.

Config file master.cf#

This file contains a list of services that will be run by the master process as daemons, they are:

pickup, cleanup, qmgr, tlsmgr, rewrite, bounce, defer, trace, verify, flush, proxymap, proxywrite, smtp, relay, showq, error, retry, discard, local, virtual, lmtp, anvil, scache, postlog, maildrop, uucp, ifmail, bsmtp, scalemail, mailman

What are they doing? I'm not sure, they come preconfigured in the master.cf as you can see and we can try to understand all of them or just focus on the two services that are relevant for our use case and leave the default ones working as Postfix expects.

The two services that we want are both running the smtpd command but the only difference is the port that we are going to expose.

  • smtpd over port 25
  • smtpd over port 587

The 25 has been the historical default SMTP port and the actual port used by SMTP servers to communicate between themselves, so when our SMTP server receives and email from an external SMTP server this communication is done using this port 25. This port accepts non encrypted communication but we are forcing to use TLS.

The port 587 is the one used by SMTP servers to receive the mail from our clients and it forces explicitly the usage of TLS.

So now understanding this, we can reuse our diagram of Alice and Bob with these services and ports:

graph LR
  Bob --> BobClient;
  BobClient -->|587| BobSMTPServer;
  BobSMTPServer -->|25| AliceSMTPServer;
  AliceSMTPServer --> AliceIMAPServer;
  AliceIMAPServer -->|143| AliceClient;
  AliceClient --> Alice;

Dovecot (v2.3.7.2)#

From the official Dovecot site we read this:

Dovecot is among the best performing IMAP servers while still supporting the standard mbox and Maildir formats. The mailboxes are transparently indexed, which gives Dovecot its good performance while still providing full compatibility with existing mailbox handling tools.

The most important for us is that this will be our IMAP and also our auth backend, both Postfix and Dovecot will use Dovecot auth backend.

Let's install it

v2.3.7.2 used in this post

$ apt install dovecot

And put these lines in /etc/dovecot/dovecot.conf:

/etc/dovecot/dovecot.conf
## MAIL GENERIC
mail_location = maildir:~/Maildir
mail_privileged_group = mail
protocols = " imap"

## AUTH
disable_plaintext_auth = no
auth_mechanisms = plain login
userdb {
  driver = passwd
}
passdb {
  args = %s
  driver = pam
}

## SERVICES: IMAP AND AUTH POSTFIX SOCKET
service imap-login {
  inet_listener imap {
    port = 143
  }
}
service auth {
  unix_listener /var/spool/postfix/private/auth {
    mode = 0666
    user = postfix
    group = postfix
  }
}

## SSL settings
ssl = required
ssl_cert = </etc/letsencrypt/live/mail.{REPLACE_YOURDOMAIN}/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.{REPLACE_YOURDOMAIN}/privkey.pem
ssl_dh=</root/dovecot/dh.pem
ssl_min_protocol = TLSv1.2

## Mailbox definitions
namespace inbox {
  inbox = yes
  mailbox Drafts {
    special_use = \Drafts
  }
  mailbox Junk {
    special_use = \Junk
  }
  mailbox Trash {
    special_use = \Trash
  }
  mailbox Sent {
    special_use = \Sent
    auto = subscribe
  }
}

Remember to replace REPLACE_YOURDOMAIN

We need to generate the dh.pem:

This will take few minutes

$ openssl dhparam 4096 > /root/dovecot/dh.pem
To understand better what is DH check the Wikipedia page  

This is enough for now, let's move to the DNS and Certificates section which is also needed for Postfix

DNS#

First of all mention that we will come back to configure DNS but we need some basics.

We need a domain name bought and somewhere to configure the DNS zones of this domain.

For example, with Google Domains you have the registrar with basic DNS Zones management where you can manage custom records.

Depending on the domain provider and the DNS zones management provider the configuration will be one or other but the concept is the same, we will need to define a couple of records:

Imagine that our domain is yourdomain.com and the IP is 1.2.3.4, the records will be:

Hostname Type Data
yourdomain.com A 1.2.3.4
mail.yourdomain.com A 1.2.3.4
yourdomain.com MX 0 mail.yourdomain.com.

The important part here is this, extracted from Wikipedia MX Record page:

When an e-mail message is sent through the Internet, the sending mail transfer agent (MTA) queries the Domain Name System for the MX records of each recipient's domain name. This query returns a list of host names of mail exchange servers accepting incoming mail for that domain and their preferences.

So we need:

  • The main A record pointing yourdomain.com to our IP
  • The main A record of our mail DNS zone mail.yourdomain.com pointing to our IP too
  • The main MX record of our yourdomain.com which is the one that will be after the @ and the one used by other SMTP servers to get the mail server mail.yourdomain.com from this MX record. Once they have the mail.yourdomain.com address they can get the IP with the A record.

Now we have to wait until the public DNS servers replicate our records. We can use dig to validate that the public DNS that we are using from our host has these records replicated, if we don't see them it can be because it's taking long to replicate or because we did something wrong:

$ dig +noall +answer +multiline yourdomain.com a
yourdomain.com.     57 IN A 1.2.3.4

$ dig +noall +answer +multiline mail.yourdomain.com a
mail.yourdomain.com.    1927 IN A 1.2.3.4

$ dig +noall +answer +multiline yourdomain.com mx
yourdomain.com.     3224 IN MX 0 mail.yourdomain.com.

With this we can request our certificates, let's move to Certificates section.

TLS Certificates#

The main goals of this step are:

  1. Encrypt our content
  2. Be more trustful

To ensure that our communication between the clients and the server and between our server and other servers is secure we need to add a layer called TLS which encrypts our content. To encrypt the content with TLS normally we use a public key and a private key. The public key can be shared anywhere and the content encrypted with this public key can be decrypted with the private key which has to be properly custodied. This is known as asymmetric encryption.

The process that we will follow is the same that we would do to use HTTPS instead of HTTP.

We need to generate a TLS certificate and a private key and both have to be signed by a Certificate Authority. This Certificate Authority is the one that gives trustiness to our TLS certificate. We can send a CSR to any trusted certificate authority and get our signed certificate and signed key which can be used to encrypt and decrypt the content and will also be trusted by third parties because they will be generated with the conditions of these CAs which are also trusted. This is known as the chain of trust. We can also generate a root certificate and declare ourselves as a CA and sign our own certificate but nobody in the public internet (like gmail) is going to trust us, that's why we need to issue our cert to a trusted CA. Normally signing a certificate by a trusted CA has an economic cost which important companies pay because that gives more trust, like a banking online platform, they want their green padlock with the name of their company there, that comes with an economic cost. But this is not our case, we can issue our signed cert to a free and automated CA like Letsencrypt and use Certbot to make the request of this signed cert a bit easier.

Certbot requests the certificates to Letsencrypt using different challenges, one of them requires access via HTTP to out host, that's why we need to have also an HTTP server running. For our case we will use NGINX.

So let's start, first we need to install Certbot and NGINX

certbot v1.30.0 used in this post nginx v1.18.0 used in this post

$ add-apt-repository ppa:certbot/certbot
$ apt update
$ apt install certbot
$ apt install nginx
 

We need our nginx service running, we can check it with systemctl status nginx and in case it's not running we can start it with systemctl start nginx

Let's create an nginx site that will be the one used to solve the challenges by Letsencrypt and validate that we are the owners of the domain name so we can get valid and trusted TLS certificates for our domain name and nobody else can:

/etc/nginx/snippets/letsencrypt.conf
1
2
3
4
5
6
7
8
location ^~ /.well-known/acme-challenge/ {
    default_type "text/plain";
    autoindex on;
    root         /var/www/letsencrypt;
}
location = /.well-known/acme-challenge/ {
    return 404;
}
/etc/nginx/sites-enabled/mail.mydomain.com
1
2
3
4
5
6
7
server {
    listen 80;
    listen [::]:80;
    server_name mail.mydomain.com;

    include /etc/nginx/snippets/letsencrypt.conf;
}

Ideally NGINX sites should be placed in the /etc/nginx/sites-available and then create a soft link from this file to /etc/nginx/sites-enabled and then reload nginx to serve this site instead of just creating them in sites-enabled, but this is just a convention so we don't "have" to. If you prefer to do things properly I recommend to do it.

Now we can validate that our HTTP server is capable of being reached by the Letsencrypt challenges. For that is as easy as placing a file to be served in the directory where the challenge will happen (the challenge is an exchange of files):

$ echo "pong" >> /var/www/letsencrypt/.well-known/acme-challenge/ping.txt

$ curl http://mail.yourdomain.com/.well-known/acme-challenge/ping.txt
pong

This last pong after the curl validates that we are exposing the needed dir properly under mail.yourdomain.com

So let's request the certificate:

$ certbot certonly \
    --webroot \
    -w /var/www/letsencrypt \
    -d mail.yourdomain.com \
    --noninteractive \
    --agree-tos \
    --email your@email.com

The answer will be something like:

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for mail.yourdomain.com

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/mail.yourdomain.com/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/mail.yourdomain.com/privkey.pem
This certificate expires on 2022-12-12.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
* Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
* Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

If you run it again and it's not ready for renewal the answer will be something like:

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Certificate not yet due for renewal

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Certificate not yet due for renewal; no action taken.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Now we have our certs in:

Certificate is saved at: /etc/letsencrypt/live/mail.yourdomain.com/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/mail.yourdomain.com/privkey.pem

This step will cover the config lines of Postfix and Dovecot referring to TLS.

Users and Clients#

As we have configured the auth with Dovecot and Dovecot is configured to use the users from the system, if we want a new email account we just need to create a user with a home and password:

$ useradd youruser -m
$ passwd youruser

Now you can configure desktop clients like Thunderbird or mobile clients like Blue Mail. Is as easy as following the wizard of the client and put the configuration of the SMTP and IMAP servers that we have configured plus the user and password that we have created and we will be ready to send and receive emails, but maybe our emails will still be blocked (or flagged as spam) by some receiver email servers like Gmail. To improve this, let's move to the next chapter Being Trustful.

Being Trustful#

First of all, let's see how we can see the original content of an email in Gmail so we can always send an email from our mail server to a Gmail account and check few things:

  • if the email arrives the Gmail account
  • if the email is flagged automatically as spam or not
  • if the email passes the SPF
  • if email passes the DKIM
  • if email passes the DMARC

We will understand better what is SPF, DKIM and DMARC later.

Show Original in Gmail#

At the time of writing, when you open an email in Gmail, at the right we can see 3 dots like this:

Image title

Once you click on them, a dropdown opens and you can click on "Show original":

Image title

Once we open the original content of the email we want to check 4 things, if the TLS is used and if the SPF, DKIM and DMARC are flagged as "PASS":

Image title Image title

Now we should see TLS used but the SPF, DKIM and DMARC shouldn't pass, so or the email has not arrived or has arrived to the spam folder.

Our goal to be trustful is to have all these three with the "PASS" flag. Let's understand each one.

Reverse DNS#

Reverse DNS lookup is used to request a domain name based on an IP (the reverse of the typical DNS behaviour). This is used to prove that the owner of this IP is also the owner of the domain, because only the owner of the IP is the one with access to the hosting provider configuration panel to setup this reverse lookup. So this is used to be more trustful, when an email server gets an email, this server can automatically retrieve the IP of that domain, and then validate that domain with the reverse lookup and check that the IP is linked to the same domain. If not, then there's risk of spoofing.

To configure this lookup there's no standard way, every hosting provider has a custom way for doing it, so the best is investingating it by yourself.

To prove that the reverse DNS lookup is properly configured you can use MXToolBox.

SPF#

SPF stands for Sender Policy Framework. An SPF record is a DNS record of type TXT which declares the address of the real mail server. This record is used to detect when a third party is trying to impersonate your address from another IP.

For more detail, let's see the description from Wikipedia:

Sender Policy Framework (SPF) is an email authentication method designed to detect forging sender addresses during the delivery of the email. SPF alone, though, is limited to detecting a forged sender claim in the envelope of the email, which is used when the mail gets bounced. Only in combination with DMARC can it be used to detect the forging of the visible sender in emails (email spoofing), a technique often used in phishing and email spam.

The server which will receive your email from your SMTP server will use your domain name to do a request to that domain and search for the SPF record, if the address of that SPF record matches the address of the sender then Gmail will flag your email with the "PASS" for the SPF.

The only thing to do is adding a DNS record like this:

Hostname Type Data
{REPLACE_YOURDOMAIN} TXT "v=spf1 ip4:{REPLACE_YOURIP}/32 -all"

Wait until the record is replicated, check it with:

$ dig +noall +answer +multiline yourdomain.com txt
yourdomain.com.     3600 IN TXT "v=spf1 ip4:1.2.3.4/32 -all"
When the response of the dig shows your SPF record then we are done and we can check it sending an email to a Gmail account, go to Show original and check if Gmail flags our SPF as PASS.

DKIM#

DKIM means DomainKeys Identified Mail and is an email authentication based on a pair of keys, one public and one private. The idea is to sign every email with the private key and then publish the public key in a DNS DKIM record (type TXT, like SPF) so the receiver can verify if the signed email matches the public key, if not, then this email has been potentially spoofed.

To sign our emails we configure Postfix to use OpenDKIM and this software will be the responsible of this action.

Installing OpenDKIM (v2.11.0)#

Let's start:

v2.11.0 used in this post

$ apt install opendkim opendkim-tools

Now configure the config file:

/etc/opendkim.conf
AutoRestart             Yes
AutoRestartRate         10/1h
UMask                   002
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
PidFile                 /var/run/opendkim/opendkim.pid
SignatureAlgorithm      rsa-sha256

UserID                  opendkim:opendkim

Socket                  inet:12301@localhost

Now let's create a directory structure that will hold the trusted hosts, key tables, signing tables and crypto keys:

mkdir -p /etc/opendkim/keys

Then we have to specify our trusted hosts:

/etc/opendkim/TrustedHosts
1
2
3
4
127.0.0.1
localhost
192.168.0.1/24
*.{REPLACE_YOURDOMAIN}

And our signing table which maps domains/email addresses and their selectors:

/etc/opendkim/SigningTable
*@{REPLACE_YOURDOMAIN} mail._domainkey.{REPLACE_YOURDOMAIN}

Now we need to specify the key table, which maps the domain with the key:

/etc/opendkim/KeyTable
mail._domainkey.{REPLACE_YOURDOMAIN} {REPLACE_YOURDOMAIN}:mail:/etc/opendkim/keys/{REPLACE_YOURDOMAIN}/mail.private

Remember to replace REPLACE_YOURDOMAIN

This private key is not created yet, let's create it:

$ cd /etc/opendkim/keys
$ mkdir {REPLACE_YOURDOMAIN}
$ cd {REPLACE_YOURDOMAIN}
$ opendkim-genkey -s mail -d {REPLACE_YOURDOMAIN}
$ chown opendkim:opendkim mail.private

Now in the directory with the name of your domain now there will appear two files, the key and the DNS TXT record data. In the mail.txt file we will see something like this:

mail._domainkey IN  TXT ( "v=DKIM1; h=sha256; k=rsa; "
  "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy+KHNcYaCf4RN2yLNPkJB+1h8OaLQywOZ+R+PlC4w0N5QBRwX4R14mTx3uEbWCEI3dqwaI6nxVv7xImeTMEmYSrOUgcsCVLKirxLeQm5N6mjdwoS8zLA5I0Y2bpJTVE7RDZ75ZuqGrI1dpQlOiNAAocbBQnj2ThfS9zh8riNN+t8/yAwq58JDVjb9pe3VvxZb3iYFgY2IDFPJ7"
  "9vVmh0mfcR7xsiR+i/ig/noLBLZErayNOoG0YR4plI9fNbvKY7sMUnVTOLi5I6TOy9xOVjV6wZa29QPXqyFOBhlOoEJ+PVtfK8yRgqNc8nxOndK1EUt0/gvw9wP3qsKe7DZifjvwIDAQAB" )  ; ----- DKIM key mail for yourdomain.com

We copy everything between the parenthesis and we are ready to create our DKIM record, from the v=DKIM1 until the whole pub key, respecting the " and spaces:

Hostname Type Data
mail._domainkey.{REPLACE_YOURDOMAIN} TXT {REPLACE_YOURDKIMDATA}

Now restart Postfix and OpenDKIM, wait until the DNS record is replaced. Check it with:

$ dig +noall +answer +multiline mail._domainkey.yourdomain.com txt
mail._domainkey.yourdomain.com. 3600 IN TXT "v=DKIM1; h=sha256; k=rsa; " "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy+KHNcYaCf4RN2yLNPkJB+1h8OaLQywOZ+R+PlC4w0N5QBRwX4R14mTx3uEbWCEI3dqwaI6nxVv7xImeTMEmYSrOUgcsCVLKirxLeQm5N6mjdwoS8zLA5I0Y2bpJTVE7RDZ75ZuqGrI1dpQlOiNAAocbBQnj2ThfS9zh8riNN+t8/yAwq58JDVjb9pe3VvxZb3iYFgY2IDFPJ7" "9vVmh0mfcR7xsiR+i/ig/noLBLZErayNOoG0YR4plI9fNbvKY7sMUnVTOLi5I6TOy9xOVjV6wZa29QPXqyFOBhlOoEJ+PVtfK8yRgqNc8nxOndK1EUt0/gvw9wP3qsKe7DZifjvwIDAQAB"

Once you can see your DKIM record then we can test our DKIM sending and email to Gmail and checking the original content and verify if now Gmail flags our email as PASS with DKIM.

DMARC#

DMARC means Domain-based Message Authentication, Reporting & Conformance. In simple, it's a layer of email authentication on top of SPF and DKIM, if SPF and DKIM are properly configured and we add a DNS record for DMARC Gmail will give us our PASS to DMARC. If we want a better description we can check the official page description:

DMARC, which stands for “Domain-based Message Authentication, Reporting & Conformance”, is an email authentication, policy, and reporting protocol. It builds on the widely deployed SPF and DKIM protocols, adding linkage to the author (“From:”) domain name, published policies for recipient handling of authentication failures, and reporting from receivers to senders, to improve and monitor protection of the domain from fraudulent email.

You only have to create a DNS record:

Hostname Type Data
_dmarc.{REPLACE_YOURDOMAIN} TXT "v=DMARC1;p=reject;"

Check it with:

$ dig +noall +answer +multiline _dmarc.yourdomain.com txt
_dmarc.yourdomain.com.  3600 IN TXT "v=DMARC1;p=reject;"

If dig responds with the data of your DNS record then we are ready.

Summarize#

Now we have everything configured, if something fails we should check step by step checking the logs, and to be able to follow step by step let's see which is the flow of an email with all these pieces:

graph LR
    client_sender --tls--> postfix_sender;
    postfix_sender == 1. auth ==> dovecot_auth --> pam;
    pam -. ok .-> dovecot_auth -. ok .-> postfix_sender;
    postfix_sender == 2. dkim ==> opendkim -. signs_email .-> postfix_sender;
    postfix_sender == 3. tls ==> smtp_server_receiver;
    smtp_server_receiver == 4 ==> validation;
    validation --> reverse_dns_lookup -. ok .-> validation;
    validation --> dns_check_spf -. ok .-> validation;
    validation --> dns_check_dkim -. ok .-> validation;
    validation --> dns_check_dmarc -. ok .-> validation;
    validation -. ok .-> smtp_server_receiver;
    smtp_server_receiver == 5 ==> imap_server_receiver;
    imap_server_receiver == 6 ==> client_receiver;

Validate trustiness#

There are multiple ways, you can search for online sites but my preferred ones are:

  1. Gmail: check original content as we've already seen.
  2. Mail Tester: https://www.mail-tester.com this gives more details. If you have everything properly setup you should see an score of 10/10 Mail Tester

And that's all, you can enjoy your new mail server :)


Last update: 2022-09-19
Created: 2022-08-30