iKVM
Secure Login
Network Cards
Hard drives
SSHFS
Basic DNS
Website DNS
Certbot SSL
Apache and PHP
Maria DB
Mail Server Basics
Mail Server SSL
Mail Server Filters
Checklist
Backup
Mail Server Filters (Dovecot Sieve)

Note: This article series covers configuring Debian 12 for hosting multiple domains and web sites on a single dedicated server. As such, some strategies may be inappropriate for your environment. Sockets for example are appropriate for communication between services hosted on the same machine but not suited to a set up with distributed services (where you'd use ports). Please consult the overview for more information.
In order to set up mail filters we need to install LMTP.
LMTP (Local Mail Transport Protocol) is a little service that allows Postfix to pass mail to Dovecot to process and store. Dovecot can then filter your messages using something called sieve.
Sieve is used by spam filters and I use it to check DNS records before receiving mail at
ben@benosborne.com (see benosborne.com/contact.php) so I need LMTP installed.
Sieve is also used to allow users to manage their own filters in some mail clients. If you aren't planning to filter your mail messages then it's probably not necessary to install LMTP.
Note that even though Dovecot will be filtering your emails, Postfix still needs to know about your virtual domain set up so it can accept, defer or reject actions attempted by unknown clients, so if you decide to install LMTP then leave those settings in place.
Installing LMTP
Update your package manager:sudo apt update
Install the LMTP service:
sudo apt install dovecot-lmtpd
Enable it in Dovecot, if you have Postfix and Dovecot on separate servers then enable the inet_listener (network port). If they're on the same server you can get away with the unix socket (unix_listener).
I had to be specific about the path, mode (permissions) and user / group for this to work on my server so I've commented out the given sections and inserted the socket configuration that worked for me. Check your Postfix logs if you stop receiving mail, it will tell you why it didn't deliver the message:
sudo nano /etc/dovecot/conf.d/10-master.conf
service lmtp { #unix_listener lmtp { # mode = 0666 #} unix_listener /var/spool/postfix/private/dovecot-lmtp { mode = 0660 user = postfix group = postfix } # Create inet listener only if you can't use the above UNIX socket #inet_listener lmtp { # Avoid making LMTP visible for the entire internet #address = #port = #} }Tell Postfix about it:
sudo nano /etc/postfix/main.cf
If you're using the network port (enter your IP address, if localhost use 127.0.0.1):
virtual_transport = lmtp:[x.x.x.x]:24If you're using the unix socket:
virtual_transport = lmtp:unix:private/dovecot-lmtpRestart the Postfix and Dovecot services to pick up the changes:
sudo systemctl restart postfix.service
sudo systemctl restart dovecot.service
Installing Sieve
Sieve is a little mail manipulation language which requires LMTP to function (make sure that's working first!).Install Sieve:
sudo apt install dovecot-sieve
Then enable it in the Dovecot LMTP settings (20-lmtp.conf, bottom of the file!):
sudo nano /etc/dovecot/20-lmtp.conf
protocol lmtp { # Space separated list of plugins to load (default is global mail_plugins). mail_plugins = $mail_plugins sieve }I want to use an external execution plugin that enables you to run a shell script so you have to tell sieve that and also specify the plugin you want to use (vnd.dovecot.execute):
sudo nano /etc/dovecot/conf.d/90-sieve.conf
sieve_plugins = sieve_extprograms --- sieve_extensions = +vnd.dovecot.execute # path where executable .sh files are stored - you need to create this path sieve_execute_bin_dir = /usr/lib/dovecot/sieve-execute ...Make that script directory:
sudo mkdir /usr/lib/dovecot/sieve-execute
Make dovecot the owner:
sudo chown dovecot:dovecot /usr/lib/dovecot/sieve-execute
Create the shell script you want to execute:
sudo nano /usr/lib/dovecot/sieve-execute/myscript.sh
Remember to add the execute permission or it won't run!
sudo chmod +x /usr/lib/dovecot/sieve-execute/myscript.sh
Create your sieve script
Edit your sieve script (virtual user home directory/.dovecot.sieve), that's dovecots mail_home setting. Remember that your mail_location setting is the user home path/mail:So you've got 2 files to consider here if you want to write an external script, the dovecot sieve file which is executed when new mail arrives (mail_home/.dovecot.sieve - sieve code) and the executable script file (shell script).
I want to create a simple script that checks a DNS server for the existence of a certain record (based on the SENDER address) and discards the email if it isn't found. To get simple feedback from a shell script you can exit 0 (True) or exit 1 (False). So my dovecot sieve script will run the external script and if it fails it will discard the email, otherwise it will deliver it as usual.
So my dovecot sieve script will look as follows. Remember! Your mail messages are in virtual home/mail. The main home folder is used for things like your sieve scripts:
sudo nano /var/mail/vmail/benosborne.com/ben/.dovecot.sieve
require ["vnd.dovecot.execute"]; if not execute :pipe "check_dns.sh" { discard; stop; }Notice that I'm not giving any kind of response if the DNS check fails because a hacker can use that rejection message to attack other servers by sending me messages from fake accounts.
Always err on the side of caution with your scripts. If they can do something malicious then rest assured someone will!
Now you need to edit your executable file (check_dns.sh) which will be in that sieve-execute directory you created earlier:
sudo nano /usr/lib/dovecot/sieve-execute/check_dns.sh
#!/bin/bash checkem () { OIFS=$IFS IFS='@' em=$1 read -ra arr <<< "$em" domain=${arr[1]} name=$(echo -n "$em" | md5sum | cut -d' ' -f1) contents=$(echo -n "$domain" | md5sum | cut -d' ' -f1) dnsrecord="${name}.${domain}" #echo ${dnsrecord} > /var/mail/vmail/benosborne.com/ben/vars.txt if [ -z $(dig +authority +short -t txt ${dnsrecord} | grep "$contents")> # NO DNS SET UP exit 1 else # DNS SET UP exit 0 fi IFS=$OIFS; } #printenv > /var/mail/vmail/benosborne.com/ben/vars.txt #echo $SENDER > /var/mail/vmail/benosborne.com/ben/vars.txt checkem "$SENDER"I've commented out a couple of lines you might find useful because echo doesn't output when you're testing so it feels like you're stumbling around in the dark a bit, you have to output to a file instead.
Uncomment the #printenv line to output all of the available environment variables to that file (vars.txt in my case but obviously set it to anything you like!) and the #echo line shows you the pattern for outputting a variable (in this case the SENDER environment variable).
You can see I use dig for the DNS lookup and just examine the contents of a TXT record.
Remember to give your shell script execute permissions:
sudo chmod +x /usr/lib/dovecot/sieve-execute/check_dns.sh
Test it with sieve-test (it doesn't execute the actions unless you specify -e). Make sure you create a test email! I downloaded 2 from Thunderbird, 1 spam and 1 from a domain where I'd created the record - both saved in my mail_home directory (the syntax for sieve-test is sieve-test < sieve script > < email >):
sudo sieve-test /var/mail/vmail/benosborne.com/ben/.dovecot.sieve /var/mail/vmail/benosborne.com/ben/spam.eml
Fails because spam so the sieve script discards it!
sudo sieve-test /var/mail/vmail/benosborne.com/ben/.dovecot.sieve /var/mail/vmail/benosborne.com/ben/good.eml
Succeeds because I set up the DNS record!
Restart Dovecot:
sudo systemctl restart dovecot.service
Now send yourself a good and a bad email and then monitor the dovecot logs to make sure the sieve filter is working! Note that I had problems sending from my Gmail (where I've got multiple alias sender addresses set up - it kept detecting the main account) and had to send from Thunderbird to get a valid SENDER.
To view the logs for Postfix or Dovecot if you find yourself needing to diagnose a problem:
sudo journalctl -r -u dovecot.service
sudo journalctl -r -u postfix@-.service