Would you like to have your own mail server that serves emails for multiple domains? Are you struggling with MX Blacklists and would like to have your emails in your possession but still send emails out without them reaching spam boxes? Then let’s set up iRedMail with SMTP relay using Brevo (or another provider). With Brevo, you can send up to 300 emails daily for free, which is enough for most people who do not run a business. Let’s dive into the plethora of open source options that can take us there!
Desired State
We want to have a running mail server that is integrated with an OPNSense firewall and terminates SSL for web services (RoundCube / iRedmail Admin) on the firewall using HAProxy.
Able to reach the mailboxes from anywhere via IMAP.
Send emails via a free SMTP relay to avoid getting on spam lists.
(missing image: image_1_failed.png)
RounCube running on HTTPS behind HAProxy
Expected Gear / Skills
Proxmox (or you can run iRedMail on another Hypervisor or bare-bone)
OPNSense (whether virtualized or bare-bone does not matter for this set up) - the steps will work similarly with Pfsense as well.
At least 3 GB RAM available for iRedMail and all the software that it comes with
HAProxy (in this guide, we will use HAProxy as a plugin of OPNSense).
A free CloudFlare account with your domain name servers using CloudFlare.
Basic Linux skills (Debian) are recommended (all required steps are covered with screenshots)
Possibility to open ports 25 (SMTP in), 465/587 (SMTPS out), 993 (IMAP), 80/443 (HTTP/HTTPS) on your firewall. If you are using a hosted VPS, check with the provider that port 25 can be opened to avoid disappointment.
About 3 hours of your time to go through the entire guide (if things go according to plan)
What you do NOT need
Static IP by your ISP or VPS hosting provider
Database skills (we will be using MariaDB SQL)
Previous experience of working with Postfix, Dovecot, iRedMail Admin, Nginx, HAProxy.
Pointing your MX records (DNS):
In this case, I have the domain I want to use to receive email on CloudFlare. So I create two records:
An MX record that points to mail.bachelor-tech.com with a priority of 10 (the number does not matter, since we will have only one mail server).
An A that expands on where does the ‘mail’ record point to, which should be the public IP address.
Note that the MX record should not be shielded by CloudFlare’s proxy service, as the other mail servers need to be able to resolve the public IP of the mail server to be able to reach it.
Generic Proxmox VM Installation:
On Proxmox, create a new VM. Have a standard net-iso for Debian 12.x or newer ready.
The VM should have at least 3 GB of RAM as the number of services installed is a lot. Recommended at least 2x CPU and at least 40 GB of space (if not more).
Once the VM is set up, note that the MAC address of the network card, so that we can add it into static DHCP on OPNSense (or your existing firewall).
Firewall - static DHCP assignment
On OPNSense, go to Services → ISC DHCPv4 → [LAN] (or whatever the interface is called for your local network).
Scroll down and click on the + sign to add a new host.
Add the MAC address and IP address for your host, save it and apply the configuration.
Firewall - Ports to open on your firewall (OPNSense):
On OPNSense, go to Firewall → Rules → WAN 1 (or whatever the name of your inteface is).
Port 80 and 443 for HAProxy (unless you have it set up already)
Port 25: required for mail server exchange.
Port 993 for IMAP over SSL
We will not need to open ports for SMTP since we will be using the relay by default. This is safer in case the public IP changes to ensure that communication can be sent out.
Firewall rules - go to Firewall → Rules → WAN 1 (or just WAN) and click on the + sign to make sure you have the following set up
Rule 1: Port 80
Action: Pass
Interface: WAN
Direction: in
TCP/IP: IPv4
Protocol: TCP
Destination: This Firewall
Destination port range: from HTTP to HTTP
Rule 2: Port 443
Same as above with the difference of the port range starting from HTTPS to HTTPS (or choose ‘other’ and manually type 443 in both boxes).
Rule 3: Port 25
Destination here is not ‘This firewall’ but ‘LAN net’ since we will be forwarding it as a NAT rule to our mail server later.
Port range starting from SMTP to SMTP (or choose ‘other’ and manually type 25 in box boxes).
Rule 4: Port 993 (IMAPS)
As before, the Destination is not ‘This firewall’ but ‘LAN net’.
Port range starts from IMAPS to IMAPS (or 993 to 993)
Rule 5: Port 465 (SMPTS)
As before,the Destination is not ‘This firewall’ but ‘LAN net’
Port range starts from SMTPS to SMPTS (or 465 to 465). Alternatively, you can use port 587 if you manually define it (then you must be consistent even with the NAT rule and how you connect your email client).
NAT rule for port 25
On OPNSense, go to Firewall → NAT → Port Forward and create a new rule.
Interface: your WAN
TCP/IP: IPv4
Protocol: TCP
Destination: WAN address
Destination port range: SMTP to SMTP (or select other and manually type in 25 to 25)
Redirect target IP: the IP of your mail server
Redirect target port: SMTP
Log it in for debugging purposes
Everything else leave as default or tweak as per your needs.
NAT rule for port 993 (IMAPS)
On OPNSense, go to Firewall → NAT → Port Forward and create a new rule.
Interface: your WAN
TCP/IP: IPv4
Protocol: TCP
Destination: WAN address
Destination port range: IMAPS to IMAPS (or select other and manually type in 993 to 993)
Redirect target IP: the IP of your mail server
Redirect target port: IMAPS
Log it in for debugging purposes
Everything else leave as default or tweak as per your needs.
NAT Rule for port 465 (SMPTS)
As above, use port 465 or SMTPS
Upon creation of the NAT rules, remember to click on the ‘Apply’ button in OPNSense to activate it.
Dynamic DNS for our ‘mail’ DNS record on CloudFlare with OPNSense
In order to make sure that our mail server is reachable even if our ISP changes the public-facing IP address, you can leverage OPNSense’s DDNS service with CloudFlare.
In CloudFlare, create a token that can handle records for your domain:
Confirm the creation of the token and save it somewhere safe.
In OPSense, go to Services → Dynamic DNS → Settings → and click on the + sign.
From the list, choose:
Service: CloudFlare
Username: the word ‘token’
Password: the long token ID that you have received from CloudFlare
Zone: your domain’s name
Hostname: the A or CNAME record that you want to edit
Check ip method - use ‘freedns’
Once done, click on the ‘Apply’ button and check the Log file under Settings to see that it completed successfully. You can even change the IP in CloudFlare to something else and wait 1-2 minutes to see that it changes in CloudFlare. Cool, hmm?
SSL termination for web services
Whereas for postfix and dovecot (IMAP), we will use the OPNSense firewall and NAT rules to the mail server and terminate SSL there, we will terminate SSL on OPNSense using haproxy for the web services.
This is required if you run more than one website and want to use your public IP for those as well (rather than just running a TCP passthrough, which does not care about hostnames, since it is L4 vs L7).
In OPNSense, go to System → Status → Plugins and make sure that the os-acme-client is installed (if not, click on the + sign to install it).
Then go to Services → ACME Client → Accounts. Create a new account with Let’s Encrypt. Fill in the name (ideally the name of the domain for easier tracking) and your email address. The email address should be something you actually use so that you can get warned in case renewal fails in the future.
Then go to the ‘Challenges Type’ section menu item on the left (within ACME Client) and add a new challenge type. The challenge type should be DNS-01 using the Cloudflare.com service. Leave some sleep time just in case. Then as your CF Account ID, copy over the ID from your CloudFlare account (after dash.cloudflare.com).
The API Token is the same as we created earlier for Dynamic DNS.
Then lastly, go to the ‘Certificates’ section within the ‘ACME Client’ menu on the left and create a new certificate using the challenge type and the cert authority you defined earlier:
Wait for a bit and check the ‘Log Files’ menu option on the left to see what the result was. If successfully created (or updated), it may look similar to the output below:
This guide covers set up on OPNSense. We will need to open traffic for nginx web server, so that we can reach the admin web interface + RoundCube, both installed on the iRedMail server.
If you do not have haproxy installed on your OPNSense instance, you can install it under System → Firmware → Packages - find os-haproxy and click on the + symbol to install it.
Under Services → HaProxy → Settings, go to Real Servers → Real Servers and add a new host. In this guide, we will use port 8082 that will be forwarded internally on the LAN to the iRedMail VM.
Make sure the ‘Enabled’ tick box is ticked.
Now go to Virtual Services → Backend pools and add a new host there:
Let’s define a condition. Go to Rules & Checks → Conditions and create a new condition:
Now we can set up a rule what to do if the condition is met. Go to Rules & Checks → Rules and create a new rule:
In the rule, we say that if the condition is met, traffic should be forwarded to the backend host we have created. Yet there is no service listening in the front-end yet to handle the traffic.
Go to Virtual Services → Public Services and unless you have a service there already, add a new one that listens on port 80.
Once done, click on the ‘Test syntax’ button and only click on ‘Apply’ if everything is okay.
Services To Be Installed On Our Mail Server (OPTIONAL READ):
nftables (optional) → a new generation iptables firewall (can be replaced by ufw)
netdata → system application monitor
amavis (amavisd-new) → analysis and filtering capabilities, works with postfix and spamassasin, uses external content filters and changes ratings of senders. Helps with the generation of the DKIM record
Config file: /etc/amavis/conf.d/50-user
Log file: stored in postfix logs by default
rsyslog → the ‘rocket-fast’ system for log processing
VM preparation on Proxmox - QEMU agent:
Install the QEMU agent (Proxmox) for better monitoring and soft shut downs (important esp. if you will be running a db on it as well).
Then on the VM, run ‘shutdown now’ and make sure the VM properly switches off.
On the Proxmox node, go to the VM:
Start the VM again and run the following command to ensure that the agent is running.
systemctl status qemu-guest-agent
You should now be able to see more details about your VM in the Proxmox console for your VM under ‘Summary’
If unsure, SSH into the Proxmox host and run:
qm agent <vmid> ping # No errors means it got through
qm agent <vmid> info # Prints out all kinds of details about the VM
iRedMail installation:
Update + set up the desired hostname
sudo apt update && sudo apt upgrade
sudo hostnamectl set-hostname mail.your-domain.com
# Modify the hosts file like this:
sudo nano /etc/hosts
127.0.0.1 mail.your-domain.com localhost
# Check that it is correct:
hostname -f
Install iRedMail and related tools (check the latest version here)
# Download the package
https://github.com/iredmail/iRedMail/archive/refs/tags/1.7.1.tar.gz
# Unpack it and go in
tar xvf 1.7.1.tar.gz
cd iRedMail-1.7.1
# Make the bash file executable and run it (it will download more packages):
chmod +x iRedMail.sh
sudo bash iRedMail.sh
A wizard will be launched, eventually. Click on ‘Yes’ to proceed.
Next, you will be asked for where to store emails - the default location is /var/vmail (as /var/mail cannot be used) - you can confirm the default, unless preferred otherwise.
Web server step - Nginx is the default (in the past, it was Apache) - this allows you to set up mailboxes as admin and will be used for RoundCube webmail, so it is definitely recommended.
Database menu - for our use, MariaDB is more than enough.
Database root password - be careful to type it correctly, as you do not get to see what you are typing and there is no re-type confirmation.
First domain name - what is the first (and could be the only) domain that you will want to be receiving emails on? This can be different from the hostname’s domain.
Postmaster’s email admin password - again, keep track of what you enter. It should be different from the DB root password.
Additional components - Roundcube replaces SoGo in terms of webmail capabilities (SoGo still runs on Apache). Others are good to install.
At the end of the wizard, confirm ‘y’ to proceed with the installation.
Once configured, you will be asked to confirm your SSH port for the firewall to ensure that you do not get locked out and then to restart the firewall:
Before restarting, take a look at the tips file to confirm that you have got the passwords typed correctly and know the interfaces:
nano iRedMail.tips
Reboot your VM.
Nginx configuration for iRedMail behind reverse proxy:
By default, nginx is configured to forward traffic from HTTP (80) to HTTPS (443). However, in our case, we terminate SSL on HAProxy and are operating on an unencrypted port 8082. So we need to tweak the config a bit to make it work.
Essentially, we can just comment out the redirect and copy over the content of the 00-default-ssl.conf file and merge it with what we have 00-default.conf. Your resulting 00-default.conf file should look as follows:
sudo nano /etc/nginx/sites-available/00-default.conf
#
# Note: This file must be loaded before other virtual host config files,
#
# HTTPS
server {
listen 8082;
listen [::]:8082;
#root /var/www/html;
#index index.php index.html;
#server_name _;
#Redirect all insecure http:// requests to https://
#return 301 https://$host$request_uri;
server_name _;
root /var/www/html;
index index.php index.html;
include /etc/nginx/templates/misc.tmpl;
include /etc/nginx/templates/ssl.tmpl;
include /etc/nginx/templates/iredadmin.tmpl;
include /etc/nginx/templates/roundcube.tmpl;
include /etc/nginx/templates/sogo.tmpl;
include /etc/nginx/templates/netdata.tmpl;
include /etc/nginx/templates/php-catchall.tmpl;
include /etc/nginx/templates/stub_status.tmpl;
}
Verify the config and reload nginx:
# Verify the nginx config:
sudo nginx -t
# Restart the nginx config
sudo systemctl reload nginx
Since we changed the default port on nginx, we need to update nftable’s config (firewall)
# Check the status of nftables:
sudo systemctl status nftables.service
# Show existing rules (port 80 will likely be there)
sudo nft list ruleset
# Change port 80 to 8082 (or whatever you chose on OPNSense under Real Servers
sudo nano /etc/nftables.conf
# Reload nftables:
sudo systemctl reload nftables.service
To get RounCube work unencrypted, go to the following path and change the force HTTP directive to ‘false’. No services need to be restarted after that.
sudo nano /opt/www/roundcubemail/config/config.inc.php
# Change the following directive from 'true' to 'false:
$config['force_https'] = false;
Now you can finally try all three management interfaces:
Just to repeat from earlier, with HAProxy in place to decrypt incoming SSL communication, in order to access RoundCube on what seems from the mail server’s point of view as HTTP, you need to disable the force_https directive in the Roundcube config file:
sudo nano /opt/www/roundcubemail/config/config.inc.php
# Find this part under //SYSTEM
$config['force_https'] = false;
Try logging in with the details you created earlier in the iRedAdmin interface.
You can observe your login (or a failure to login) by looking at the end of this file:
sudo tail -n 3 /var/log/mail.log
At this point, you can try sending an email to your new mailbox from outside - providing that port 25 on your firewall is unblocked and points to the IP address of your mail server on the LAN.
If the email is not arriving into your mailbox and there is nothing in the /var/log/mail.log file, it is likely being blocked by your firewall.
If you use OPNSense, you can check it in the section called Firewall → Log files → Live view.
Expand the view to at least a 100 items and watch it as you send an email to your mailbox.
Add two filters - one that is ‘Action - is - block’ (click on the + sign) and then one that says dst_port (destination port) - is - 25 (again click on the + sign).
If you see repeated attempts to reach port 25 then you know that your firewall rule is not set up correctly.
If the email arrives correctly, you will be able to access it in RoundCube:
You can review the test mail’s header to confirm that amavis service on our mail server provided virus scanning (together with SpamAssassin):
This does not yet mean that you can send emails out, as your mail server and domain does not have its own DKIM, SPF and DMARC DNS records set up, nor have we set up an SMTP relay yet.
CertBot Certificate Creation
Up to this point, we did not need to handle SSL on the mail server. However, in order to securely handle mail exchange, we will need to provide a cert.
If you would like to check all the services running on your mail server up to this point, run sudo service --status-all to ponder on your achievements so far.
There are two ways of how to install and configure certbot - either by using your distributions default package manager, which means always being on an older version that may not support the newest features but is easier to maintain, OR by using a virtual python environment where you can stay away from the default package manager (i.e. APT).
In our case, since we plan to use the CloudFlare plugin using API token (that we used earlier for OPNSense), as of the time of writing the article (10-2024), the package distribution version only supports the global API key, which is less safe. For the purpose of this article, both ways are provided:
OPTION 1: Use API token (recommended)
# WITH TOKEN
sudo apt install python3-pip
sudo apt install python3-venv
# Create a new virtual environment for python3
sudo python3 -m venv /opt/certbot/
# Upgrade pip to the newest version
sudo /opt/certbot/bin/pip install --upgrade pip
# Install certbot in the virtual environment (to get the most recent version versus APT)
sudo /opt/certbot/bin/pip install certbot certbot-dns-cloudflare
# Make the certbot command reachable globally
sudo ln -s /opt/certbot/bin/certbot /usr/bin/certbot
# Create an .ini file and protect it to store the API key:
sudo mkdir /root/.secrets
sudo chmod 0700 /root/.secrets/
sudo touch /root/.secrets/cloudflare.ini
sudo chmod 0400 /root/.secrets/cloudflare.ini
# Open the file and append this line - replace XYZ with your actual API key:
sudo nano /root/.secrets/cloudflare.ini
dns_cloudflare_api_token = "XYZ"
# Automate updates to pip + certbot + certbot-dns-cloudflare via crontab
sudo nano crontab -e
# Append the following two lines:
# Update pip + certbot + CloudFlare plugin monthly
@monthly sudo /opt/certbot/bin/pip install --upgrade pip certbot certbot-dns-cloudflare >/dev/null 2>&1
OPTION 2: Use Global API key:
# WITH GLOBAL API KEY
# Install certbot
sudo apt install -y certbot python3-certbot-dns-cloudflare
sudo mkdir /root/.secrets
sudo chmod 0700 /root/.secrets/
sudo touch /root/.secrets/cloudflare.ini
sudo chmod 0400 /root/.secrets/cloudflare.ini
sudo nano /root/.secrets/cloudflare.ini
dns_cloudflare_email = "your-cloudflare-email-address"
dns_cloudflare_api_key = "your-global-api-key-not-token"
# Create a cert via the CloudFlare DNS route
sudo certbot certonly --dns-cloudflare --dns-cloudflare-credentials /root/.secrets/cloudflare.ini -d mail.your-domain.tld --preferred-challenges dns-01
Review your certificates - you will see that those are symlinks that lead to the archive directory.
sudo ls -lah /etc/letsencrypt/live/mail.your-domain.tld/
There are four .pem files:
cert.pem → the public part of the key
chain.pem → an additional intermediary certificate in order to validate the server certificate
fullchain.pem → all certs inc. the server certificate
privkey.pem → the private part of the key that is only known to your server
Automate the renewal by the following:
# Test a renewal
sudo certbot renew -w /var/www/html/ --dry-run
# Open crontab
sudo crontab -e
# Append the following two lines:
# Certbot: Check if renewal is needed weekly and reload affected services
@weekly certbot renew -w /var/www/html/ --quiet && systemctl reload dovecot postfix nginx
Apply the certificates - Nginx
While for nginx, we do not need to apply the cert, since we terminate SSL at HAProxy, it may still be a good practice in case we implement SSL termination at the mail server in the future, instead.
Edit the following ssl template file:
sudo nano /etc/nginx/templates/ssl.tmpl
# Find the following:
ssl_certificate /etc/ssl/certs/iRedMail.crt;
ssl_certificate_key /etc/ssl/private/iRedMail.key;
# Replace them with:
ssl_certificate /etc/letsencrypt/live/mail.your-domain.tld/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mail.your-domain.tld/privkey.pem;
Run a syntax check and reload nginx:
sudo nginx -t
sudo systemctl reload nginx
Apply Certificates - Dovecot (IMAP)
Since we plan to connect to the mail server directly to sync emails with an email client via IMAPS, we will need to set the certificates on Dovecot to secure the communication over SSL port 993 (instead of unencrypted 143). POP3 is rarely used anymore (although port 995 would do the job for that).
sudo nano /etc/dovecot/dovecot.conf
# Find these two lines
ssl_cert = </etc/ssl/certs/iRedMail.crt
ssl_key = </etc/ssl/private/iRedMail.key
# Replace them with:
ssl_cert = </etc/letsencrypt/live/mail.your-domain.tld/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.your-domain.tld/privkey.pem
# Exit nano and reload the config
sudo systemctl reload dovecot
Apply Certificates - Postfix
While we plan to use an SMTP relay, in order to support SMTPS to connect to our mailbox from an external device, we need to set up the certificates.
sudo nano /etc/postfix/main.cf
# Find these three lines:
smtpd_tls_key_file = /etc/ssl/private/iRedMail.key
smtpd_tls_cert_file = /etc/ssl/certs/iRedMail.crt
smtpd_tls_CAfile = /etc/ssl/certs/iRedMail.crt
# Replace them with:
smtpd_tls_key_file = /etc/letsencrypt/live/mail.your-domain.tld/privkey.pem
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.your-domain.tld/cert.pem
smtpd_tls_CAfile = /etc/letsencrypt/live/mail.your-domain.tld/chain.pem
# Exit nano and reload the config
sudo systemctl reload postfix
smtpd → an SMTP daemon (i.e. background process) for handling incoming mail and routing to the appropriate internal location.
smtp → an SMTP daemon that delivers mail out to the world → we plan to set it up for use with an SMTP relay.
Therefore if port 25 is not enabled on firewall, postfix cannot receive emails from other mail servers to handle them internally.
When viewing logs located in /var/log/mail.log, you may come across these commands / expressions:
Client → the connecting HOST information (not the from/to directives in the header of an email).
Sender → the envelope email address supplied in the ‘mail from’ command (this is different from the From directive in the header of an email).
HELO → the original command that initiates the SMTP session (includes domain name or the IP of the mail server).
EHLO → extended HELLO, to tell the server that it is an SMTP client (newer standard) and what does it support (mime / auth)
STARTTLS → TLS handshake information - when used, the SMTP connection is reset back to EHLO and the receiving mail server provides a TLS certificate and its public encryption key. The email client (the one connecting machine that wants to deliver email) verifies it legitimacy and generates shared secret key used in the session. The receiving mail server decrypts the key and indicates ETRN (i.e. that it is ready to receive DATA/BDAT).
Explicit (opportunistic) TLS → communication starts un-encrypted and the sending email client requests to switch to TLS after the initial EHLO.
Implicit (forced) TLS → the sending email client requires TLS at the start of the exchange in the initial EHLO. Communication does not continue unless the receiving mail server can confirm that this is supported.
ETRN → is used during the communication exchange between the sending and receiving mail server. It is a separate connection from SMTP via which an email is delivered. It is used to indicate that the mail server is ready to receive traffic. Originally used by sites that had intermittent internet connection.
DATA → once EHLO and ETRN are established, the client (i.e. the connecting machine) asks the receiving mail server to supply the mail data via SMTP. The receiving server responds with 354 response code to confirm and the transfer starts with the header (inc. the date, from, subject, to) and the body of the email text - line by line until a dot is reached to indicate the end.
BDAT → similar to DATA, a product of MS Exchange. Has to arguments - length of data chunk in octets + the content. Does not use the dot at the end to indicate the end since it is provided by the count of the data chunk in the first argument.
RCPT → recipient (addressed to)
VRFY → verify if the given mailbox does actually exist in the destination (receiving) mail server. Can be used by an intruder (spammer) to find out whether the address exists or not. Therefore, security hardening is important here.
EXPN → similar to VRFY but used with mail lists (groups) - with the same security hardening requirements.
RSET → erases buffers and returns the SMTP connection to its original state with a response code 250.
QUIT → terminates the SMTP session with the sending mail server. The receiving mail server sends code 221.
Understanding general spoofing protection types:
SPF → a TXT DNS record that identifies which IP addresses are authorized (trusted) to send emails out from your domain (i.e. from your mail server). The receiving email client (to which you are delivering email from your mail server) can then verify that is coming from the authorized origin. The process has three steps:
Step 1 - policy establishment (as per our TXT DNS record) - which hosts can send emails out
Step 2 - DNS server lookup - the receiving mail server verifies that the IP matches the DNS name
Step 3- Response - the receiving mail server either accepts, flags or rejects the email
Tags (some of them):
<v=spf1> → spf version
→ authorized incoming mail servers for the domain
→ other systems authorized to send on behalf of the domain.
→ all other trusted external domains that can send on behalf of our domain (such as Mail Chimp, Brevo).
→ catch-all mechanism for anything not caught in the other rules (typically all else is not authorized)
Example simple record: "v=spf1 mx ~all” → SPF version 1, allow all mail servers under the MX record and for all others, mark them as suspicious.
DKIM → Domain Keys Identified Mail is a digital certificate supplied with an outgoing email from your mail server. It allows the receiving client to verify that the header or body of the email was not modified during transmission. The receiving mail server fetches the public part of the key from the DNS record.
Tags (some of them):
<v=1> → version
public key
→ time stamp
<a=rsa-sha256> → cryptographic algorithm used for creating the signature
→ selector tag for finding the public key in the DNS record
<d=domain.com> → accompanies the selector tag by specifying the (sub)domain name used by the sender (our mail server)
Example record: subdomain_domainkey TXT 3600 "v=DKIM1; a=rsa; p=MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDU2+0C4M94mNWIk..."
DMARC → leverages both SPF + DKIM alignment tests but specifies its own policy (set of instructions to the receiving mail server where we are sending an email to on how to proceed). Policy is either ‘none’ (do nothing), ‘quarantine’ (keep for a while, evaluate) and ‘reject’ (do not deliver).
The receiving mail server will then return a report of all the emails that were not delivered (as feedback). It includes a number of tags:
→ % of failed policy should comply with (e.g. only certain % to quarantine) - value 1 to 100
→ specific policy for emails from subdomains (e.g. p = none means no subdomains)
→ level of strictness DMARC should use for DKIM. Default is ‘relaxed’ (only SPF or DKIM needs to pass), ‘strict’ is another option
→ same as adkim but for the SPF requirements - strict/relaxed options (e.g. are emails from subdomain allowed or do emails have to match the ‘from’ envelope from the domain with the ‘return-to’ address?
→ how often to receive aggregate reports (in seconds - default is 86400 = every 24 hours).
→ settings for forensics report. Value 0 = send a report if all checks fail (DKIM + SPF), 1 = if one of them fails, D = if DKIM fails, S = if SPF fails. Default is 0.
→ aggregated reports; sent daily by default with no personal identifiable information (PII), supplied in XML
→ forensic reports; individual report sent instantly after a failure to meet policy, it contains PII information, the format is in plain text
Example: "v=DMARC1; p=quarantine; pct=100;rua=mailto:[email protected]" → version 1, if policy is not met then quarantine, screen 100% of emails, send reports of emailing failing the policy to this address.
smtpd_sender_restrictions → restrictions in the context of the MAIL FROM information, i.e. from the envelope email address supplied by the sender (e.g. [email protected]). Note that this is not the FROM directive in the header of the email as that is part of the DATA stream - see above.
Postfix’s Directive
Explanation
reject_sender_login_mismatch
An alias to two parameters if SASL auth is passed but the MAIL FROM does not match.
reject_non_fqdn_sender
A numerical response to rejecting the request when the RCPT TO address specifies a domain that is not in fully-qualified domain form.
reject_unlisted_sender
Rejects mail from unknown sender addresses, even when no explicit reject_unlisted_sender access restriction is specified (default is 'no', this sets it to 'yes').
permit_mynetworks
Lookup the variable $mynetworks and treat the IP addresses of those clients as trusted.
permit_sasl_authenticated
Respond with a pass to a client that is successfully authenticated via the RFC 4954 (AUTH) protocol.
reject_unauth_destination
Proceed only if the RCPT TO domain matches what we have defined in $relay_domains (if forwarding) or $mydestination (if delivering).
reject_invalid_helo_hostname
Reject if EHLO is malformed (may need to add 'smtpd_helo_required = yes' as it only works if HELO/EHLO are sent).
reject_unknown_sender_domain
Reject if Postfix is not the final destination for the sender and the MAIL FROM domain has either no DNS MX record, no DNS A record, or has a malformed MX record.
reject_unauth_pipelining
Reject the request if the email client sent SMTP commands before asked (such as piping EHLO commands or ESMTP commands before asked).
smtpd_client_restrictions → restrictions in the context of the connecting HOST (e.g. mail.domain.com) - to set boundaries related to the client connection request.
Postfix’s Directive
Explanation
permit_mynetworks
Lookup the variable $mynetworks and treat the IP addresses of those clients as trusted.
permit_sasl_authenticated
Respond with a pass to a client that is successfully authenticated via the RFC 4954 (AUTH) protocol.
reject_unauth_destination
Proceed only if the RCPT TO domain matches what we have defined in $relay_domains (if forwarding) or $mydestination (if delivering).
Do you find it hard to believe that these restrictions are important? Just review your mail.log after a few days of it being online:
The next item on the list related to postfix is to ensure that unauthorized senders outside of our domain cannot use our SMTP as an open relay. If you were to start receiving undeliverable emails related to mails you never sent out, it is most likely due to that.
sudo nano /etc/postfix/main.cf
# Find this directive and match it to the following:
smtpd_sender_restrictions =
reject_sender_login_mismatch,
reject_non_fqdn_sender,
reject_unlisted_sender,
permit_mynetworks,
permit_sasl_authenticated,
reject_unauth_destination,
reject_invalid_helo_hostname,
reject_unknown_sender_domain,
reject_unauth_pipelining
# Add a new directive that verifies the client (i.e. the connecting machine) as well
smtpd_client_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
# Exit nano and perform a check + reload the config
sudo postfix check
sudo systemctl reload postfix
SMTP Relay with Brevo
While we could set up DKIM, SPF and DMARC DNS records for our mail server / domain, the likelihood is that your public IP is on one of the MX blacklists and you will struggle getting unlisted. What is more, if you are running your mail server on a ISP-provided non-static IP address, the address may change over time and you would face issues again. For this reason, it is best to use an SMTP relay.
The advantage of using Brevo (formerly Sendinblue) is that they allow for 300 emails a day for free, an offering that is unmatched by other providers (like MailTrap, MailChimp, etc.).
Click on the ‘Add domain’ button and add your domain.
If you use CloudFlare or a similar provider that Brevo supports, you can have Brevo add the required DNS records for you. Once done, you may have to wait a few hours for DNS to propagate.
In case you get a warning message that one of these parameters are being overridden, it means that the main.cf file contains this directive already - please remove the duplicates.
Now log into Roundcube and attempt to send out an email
After sending it, check the log by running sudo vi /var/log/mail.log (press SHIFT + G to go to the end; then to exit the vim text editor, type : followed by q! and press enter to confirm the exit).
You can also check the Logs and Statistics section in Brevo:
Other useful commands:
# To rebuild the index file if you make changes to the sasl_passwd file:
sudo postmap /etc/postfix/sasl_passwd
# Flush an existing email queue
sudo systemctl restart postfix
# Remember to restart postfix after changes to the config files
sudo systemctl restart postfix
Enable logging for ClamAV
Open the ClamAV’s config file and uncomment the LogFile.
Set a max size of your log file.
sudo nano /etc/clamav/clamd.conf
#LogFile /var/log/clamav/clamav.log --> uncomment this line by removing the # sign
LogFileMaxSize = 20M
# Reload ClamAV's daemon
sudo systemctl reload clamav-daemon
Test Your Outgoing Email Ranking
You can use mail-tester.com for up to three tests in 24 hours.
Once you reach the website, you will be asked to send an email to a unique email address / hostname.
Save the email address and create a new email, for example via Roundcube
Then click on the ‘Check your score’ button to see what you get:
Hopefully the boat has successfully travelled to new lands and the score is close to 10. With the free version of Brevo, an invisible tracking pixel is inserted by the relay and for some reason, it does not contain an alt tag, which lowers the score:
While in my tests, emails to all major providers inc. Google and O365 were delivered fine, if you encounter issues, reach out to Brevo’s support to manually remove the tracking from all transactional emails.
An alternative is to sign up for another provider. For instance, Mailer Lite provides 3,000 emails per month for free.
Connect Your Device To The Mail Server Via Email Client
While the choice of an email client is beyond the scope of our article, we will at least verify that it works using a BetterBird (Windows / Mac OS / Linux).
Download & install Betterbird (an improved Thunderbird fork) - or use any other email client of your choice.
Then enter your login details to your mailbox and either click on the ‘Continue’ button if you are feeling lucky or tick the ‘Configure manually’ button. Usually, the email client will be able to detect the port numbers and the host name but will not work out the ‘Username’ field - make sure it is the full [email protected], as shown below:
Try sending an email out and then replying to it. If one of these two things do not work out, verify the following:
Firewall and NAT rules
Check the mail log in /var/log/mail.log
Make sure dovecot and postfix are running on your mail server