Mail server setup (Postfix, Dovecot, MySQL, SpamAssasin)

Mail server setup (Postfix, Dovecot, MySQL, SpamAssasin)


A “short” guide on Mail server setup.  We’re going to rely on Postfix, Dovecot, MySQL and anti-spam including some details related to OpenDKIM/SPF/ReversePTR.

Install Packages For Mail Server Setup

First of all, we need to install required packages as a first step in mail server setup.

apt-get install postfix postfix-mysql dovecot-core dovecot-imapd dovecot-lmtpd dovecot-mysql

In postfix configuration, select Internet Site.

Create a MySQL Database, Virtual Domains, Users and Aliases

We’re going to create a MiSQL DB, containing 3 different tables:

  • Domains
  • Users
  • Aliases

Login to MySQL and create servermail database:

mysql -u root -p

First create a new user, specific for mail authentication:

mysql > GRANT SELECT ON servermail.* TO 'usermail'@'' IDENTIFIED BY 'mailpassword';

Use that DB to create tables and data:

mysql> USE servermail;

Create domains tables for domains recognized as authorized domains:

CREATE TABLE `virtual_domains` (
`name` VARCHAR(50) NOT NULL,

User table, for email addresses and passwords, associating each user with a domain:

CREATE TABLE `virtual_users` (
`domain_id` INT NOT NULL,
`password` VARCHAR(106) NOT NULL,
`email` VARCHAR(120) NOT NULL,
UNIQUE KEY `email` (`email`),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE

Aliases table, for specifying emails that are going to be forward to another email:

CREATE TABLE `virtual_aliases` (
`domain_id` INT NOT NULL,
`source` varchar(100) NOT NULL,
`destination` varchar(100) NOT NULL,
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE

With structure set, moving to data segment.

Virtual Domains

Insert domain(s):

INSERT INTO `servermail`.`virtual_domains`
(`id` ,`name`)
('1', ''),
('2', '');

Virtual Emails

Create email address and passwords for each domain:

INSERT INTO `servermail`.`virtual_users`
(`id`, `domain_id`, `password` , `email`)
('1', '1', ENCRYPT('firstpassword', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))), ''),
('2', '1', ENCRYPT('secondpassword', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))), '');

Virtual Aliases

Create alias that we are going to forward to some other emal address (destination):

INSERT INTO `servermail`.`virtual_aliases`
(`id`, `domain_id`, `source`, `destination`)
('1', '1', '', '');

EncFS (optional)

As you can see  in the picture above (a warning while installing), EncFS is not the ideal one. It has some known vulnerabilities, so maybe some other alternatives like CryFS  might be better idea. For this example, we’ll stick to EncFS.

$ apt-get install encfs
mkdir /encrypted-mail /decrypted-mail
chgrp mail /decrypted-mail/
chmod -R g+rw /decrypted-mail/
gpasswd -a mail fuse
chgrp fuse /dev/fuse; chmod g+rw /dev/fuse
root@li212-205:~# encfs /encrypted-mail /decrypted-mail -o --public
Creating new encrypted volume.
Please choose from one of the following options:
 enter "x" for expert configuration mode,
 enter "p" for pre-configured paranoia mode,
 anything else, or an empty line will select standard mode.
?> p
Paranoia configuration selected.
Configuration finished.  The filesystem to be created has
the following properties:
Filesystem cipher: "ssl/aes", version 3:0:2
Filename encoding: "nameio/block", version 3:0:1
Key Size: 256 bits
Block Size: 1024 bytes, including 8 byte MAC header
Each file contains 8 byte header with unique IV data.
Filenames encoded using IV chaining mode.
File data IV is chained to filename IV.
File holes passed through to ciphertext.
-------------------------- WARNING --------------------------
The external initialization-vector chaining option has been
enabled.  This option disables the use of hard links on the
filesystem. Without hard links, some programs may not work.
The programs 'mutt' and 'procmail' are known to fail.  For
more information, please see the encfs mailing list.
If you would like to choose another configuration setting,
please press CTRL-C now to abort and start over.
Now you will need to enter a password for your filesystem.
You will need to remember this password, as there is absolutely
no recovery mechanism.  However, the password can be changed
later using encfsctl.
New Encfs Password:
Verify Encfs Password:

It’s that simple.  /decrypted-mail is now a regular directory.  /encrypted-mail is that same data, just encrypted.

Configure Postfix

What is Postfix? It is Wietse Venema’s mail server (an “MTA” in the email lingo), that started life at IBM research as an alternative to the widely-used Sendmail program. Essentially it runs SMTP, and delivers incoming mail to Dovecot. As a next step in mail server setup, we are going to configure Postfix to handle the SMTP connections and messages for each user introduced in the MySQL Database.

Just in case, create backup(s) for the config files we’re going to change. Open the file to modify it:

nano /etc/postfix/

First we need to comment the TLS Parameters and append other parameters. You can use the Free SSL certificates and the paths that are suggested:

# TLS parameters
#smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
#smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache 
smtpd_tls_auth_only = yes

or you could modify depending on your personal configuration, for instance using Let’s encrypt.

# TLS parameters

Then we are going to append the following parameters below the TLS settings that we have changed in the previous step:

smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_recipient_restrictions =

We need to comment the mydestination default settings and replace it with localhost. This change allows your VPS to use the virtual domains inside the MySQL table.

#mydestination =,,, localhost
mydestination = localhost

Verify that myhostname parameter is set with your FQDN.

myhostname =

Append the following line for local mail delivery to all virtual domains listed inside the MySQL table.

virtual_transport = lmtp:unix:private/dovecot-lmtp

Finally, we need to add these three parameters to tell Postfix to configure the virtual domains, users and aliases.

virtual_mailbox_domains = mysql:/etc/postfix/
virtual_mailbox_maps = mysql:/etc/postfix/
virtual_alias_maps = mysql:/etc/postfix/

We are going to create the final three files that we append in the file to tell Postfix how to connect with MySQL.

First we need to create the file. It’s necessary to change the values depending your personal configuration.

nano /etc/postfix/
user = usermail
password = mailpassword
hosts =
dbname = servermail
query = SELECT 1 FROM virtual_domains WHERE name='%s'

Then we need to restart Postfix.

service postfix restart

We need to ensure that Postfix finds your domain, so we need to test it with the following command. If successful, it should return 1:

postmap -q mysql:/etc/postfix/

Then we need to create the file.

nano /etc/postfix/ 
user = usermail
password = mailpassword
hosts =
dbname = servermail
query = SELECT 1 FROM virtual_users WHERE email='%s'

Restart Postfix again:

service postfix restart

At this moment we are going to ensure Postfix finds your first email address with the following command. It should return 1 if successful:

postmap -q mysql:/etc/postfix/

Finally, we are going to create the last file to configure the connection between Postfix and MySQL:

nano /etc/postfix/
user = usermail
password = mailpassword
hosts =
dbname = servermail
query = SELECT destination FROM virtual_aliases WHERE source='%s'

Restart Postfix again:

service postfix restart

We need to verify that Postfix can find your aliases. Enter the following command and it should return the mail that’s forwarded to the alias:

postmap -q mysql:/etc/postfix/

If you want to enable port 587 to connect securely with email clients, it is necessary to modify the /etc/postfix/ file

nano /etc/postfix/

We need to uncomment these lines and append other parameters:

submission inet n       -       -       -       -       smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject

In some cases, we need to restart Postfix to ensure port 587 is open.

service postfix restart

Note: You can use “netstat -tulpn” cmd to check if your system listens on specific ports, or you can use this tool to scan your domain ports and verify that port 25 and 587 are open.

Configure Dovecot

Dovecot, an “LDA” (Local Delivery Agent) in the email lingo,  is an open source IMAP and POP3 email server for Linux/UNIX-like systems, written with security in mind. Essentially it runs IMAP.

Backup 7 files that are about to be modified, so that you could revert it to default if you needed to:

cp /etc/dovecot/dovecot.conf /etc/dovecot/dovecot.conf.orig
cp /etc/dovecot/conf.d/10-mail.conf /etc/dovecot/conf.d/10-mail.conf.orig
cp /etc/dovecot/conf.d/10-auth.conf /etc/dovecot/conf.d/10-auth.conf.orig
cp /etc/dovecot/dovecot-sql.conf.ext /etc/dovecot/dovecot-sql.conf.ext.orig
cp /etc/dovecot/conf.d/10-master.conf /etc/dovecot/conf.d/10-master.conf.orig
cp /etc/dovecot/conf.d/10-ssl.conf /etc/dovecot/conf.d/10-ssl.conf.orig

Edit configuration file from Dovecot.

nano /etc/dovecot/dovecot.conf

Verify this option is uncommented.

!include conf.d/*.conf

We are going to enable protocols (add pop3 if you want to) below the !include_try /usr/share/dovecot/protocols.d/*.protocol line.

!include_try /usr/share/dovecot/protocols.d/*.protocol
protocols = imap lmtp

Then we are going to edit the mail configuration file:

nano /etc/dovecot/conf.d/10-mail.conf

Find the mail_location line, uncomment it, and put the following parameter:

mail_location = maildir:/var/mail/vhosts/%d/%n

Find the mail_privileged_group line, uncomment it, and add the mail parameter like so:

mail_privileged_group = mail

Ensure permissions are 775 like this:

>drwxrwsr-x 3 root vmail 4096 Jan 24 21:23 /var/mail

We are going to create a folder for each domain that we register in the MySQL table:

mkdir -p /var/mail/vhosts/

Create a vmail user and group with an id of 5000

groupadd -g 5000 vmail 
useradd -g vmail -u 5000 vmail -d /var/mail

We need to change the owner of the /var/mail folder to the vmail user.

chown -R vmail:vmail /var/mail

Then we need to edit the /etc/dovecot/conf.d/10-auth.conf file:

nano /etc/dovecot/conf.d/10-auth.conf

Uncomment and/or set following lines:

disable_plaintext_auth = yes
auth_mechanisms = plain login
#!include auth-system.conf.ext

We need to create the /etc/dovecot/dovecot-sql.conf.ext file with your information for authentication:

nano /etc/dovecot/conf.d/auth-sql.conf.ext

Enter following code:

passdb {
  driver = sql
  args = /etc/dovecot/dovecot-sql.conf.ext
userdb {
  driver = static
  args = uid=vmail gid=vmail home=/var/mail/vhosts/%d/%n

We need to modify the /etc/dovecot/dovecot-sql.conf.ext file with our custom MySQL information:

nano /etc/dovecot/dovecot-sql.conf.ext

Uncomment the driver parameter and set mysql as parameter:

driver = mysql
connect = host= dbname=servermail user=usermail password=mailpassword
default_pass_scheme = SHA512-CRYPT
password_query = SELECT email as user, password FROM virtual_users WHERE email='%u';

Change the owner and the group of the dovecot folder to vmail user:

chown -R vmail:dovecot /etc/dovecot
chmod -R o-rwx /etc/dovecot

Open and modify the /etc/dovecot/conf.d/10-master.conf file (be careful because different parameters will be changed).

nano /etc/dovecot/conf.d/10-master.conf

##Uncomment inet_listener_imap and modify to port 0
service imap-login {
  inet_listener imap {
    port = 0

#Create LMTP socket and this configurations
service lmtp {
   unix_listener /var/spool/postfix/private/dovecot-lmtp {
	   mode = 0600
	   user = postfix
	   group = postfix
  #inet_listener lmtp {
    # Avoid making LMTP visible for the entire internet
    #address =
    #port =

Modify unix_listener parameter to service_auth like this:

service auth {

  unix_listener /var/spool/postfix/private/auth {
  mode = 0666
  user = postfix
  group = postfix

  unix_listener auth-userdb {
  mode = 0600
  user = vmail
  #group =

  #unix_listener /var/spool/postfix/private/auth {
  # mode = 0666

  user = dovecot

Also modify service auth-worker like this:

service auth-worker {
  # Auth worker process is run as root by default, so that it can access
  # /etc/shadow. If this isn't necessary, the user should be changed to
  # $default_internal_user.
  user = vmail

Finally, we are going to modify the SSL configuration file from Dovecot (skip this step if you are going to use default configuration).

# nano /etc/dovecot/conf.d/10-ssl.conf

ssl = required

ssl_cert = </etc/ssl/certs/dovecot.pem
ssl_key = </etc/ssl/private/dovecot.pem

or as we mentioned earlier, set Let’s encrypt keys:

ssl_cert = </etc/letsencrypt/live/<>/fullchain.pem
ssl_key = </etc/letsencrypt/live/<>/privkey.pem

Generating SSL certs:

openssl req -new -x509 -days 1000 -nodes -out "/etc/ssl/certs/dovecot.pem" -keyout "/etc/ssl/private/dovecot.pem"


Furthermore, you should check that port 993 is open and working (in case you enable pop3; you should check also port 995).

telnet 993

Congratulations. You have successfully configured your mail server and you may test your account using an email client:

- Username:
- Password: email1's password

Note: use port 993 for secure IMAP and port 587 or 25 for SMTP.

Avoiding SPAM filters (SPF, DKIM, DMARC)

We’re going to setup SPF, DKIM, DMARC and (maybe) PTR. Due to the size of this post/tutorial, we’re going to move these to individual posts:

SPF Setup

Check SPF Postfix Setup.

DKIM Setup

Check OpenDKIM Postfix Setup.


Check DMARC Postfix Setup.

Reverse PTR

The Reverse DNS is one of the basic requirements for running some Internet protocols. It is also often used as a spam filter to determine whether the IP address of the incoming message matches an authenticated domain name and to block the message if it doesn’t.


Spam can be annoying and dangerous, so its always a good idea to set some anti-spam services. We’ll include few: dspam and SpamAssasin.  Postfix domain block is also usefull at times.

Postfix Block Domains

To block Emails to/from specific domains, start by creating the access list file (e.g. /etc/postfix/access):

$ nano /etc/postfix/access

In there, add domain names followed by the action (DISCARD or REJECT). We recommend DISCARD: DISCARD REJECT

Tell postfix to use that file. Edit and add/update smtpd_sender_restrictions header:

$ nano /etc/postfix/
smtpd_sender_restrictions = check_sender_access hash:/etc/postfix/access

After you finish, postmat the file to prepare postfix to use it (every time you modify the file).

$ postmap /etc/postfix/access

Restart Postfix.


apt-get install dspam dovecot-antispam postfix-pcre dovecot-sieve

patch these in /etc/dspam/dspam.conf:

Home /decrypted-mail/dspam
TrustedDeliveryAgent "/usr/sbin/sendmail"
UntrustedDeliveryAgent "/usr/lib/dovecot/deliver -d %u"
Tokenizer osb
IgnoreHeader X-Spam-Status
IgnoreHeader X-Spam-Scanned
IgnoreHeader X-Virus-Scanner-Result
IgnoreHeader X-Virus-Scanned
IgnoreHeader X-DKIM
IgnoreHeader DKIM-Signature
IgnoreHeader DomainKey-Signature
IgnoreHeader X-Google-Dkim-Signature
ParseToHeaders on
ChangeModeOnParse off
ChangeUserOnParse full
ServerPID               /var/run/dspam/
ServerDomainSocketPath  "/var/run/dspam/dspam.sock"
ClientHost      /var/run/dspam/dspam.sock

Create the directory:

mkdir /decrypted-mail/dspam
chown dspam:dspam /decrypted-mail/dspam

As best as I can tell, the preferences in /etc/dpsam/dspam.conf are completely ignored. If you want to edit them, the right place is /etc/dspam/default.prefs. Totally logical, right? Anyway, patch these values:

spamAction=deliver         # { quarantine | tag | deliver } -> default:quarantine
signatureLocation=headers  # { message | headers } -> default:message

Edit /etc/postfix/

dspam     unix  -       n       n       -       10      pipe
  flags=Ru user=dspam argv=/usr/bin/dspam --deliver=innocent,spam --user $recipient -i -f $sender -- $recipient
dovecot   unix  -       n       n       -       -       pipe
  flags=DRhu user=mail:mail argv=/usr/lib/dovecot/deliver -f ${sender} -d ${recipient}


nano /etc/postfix/dspam_filter_access
/./   FILTER dspam:unix:/run/dspam/dspam.sock

Patch the end of /etc/postfix/

# new settings for dspam
dspam_destination_recipient_limit = 1 #only scan one mail at a time
smtpd_client_restrictions =
   permit_sasl_authenticated #localhost doesn't get scanned
   check_client_access pcre:/etc/postfix/dspam_filter_access #run dspam on everything else

Integrating dspam with imap:

nano /etc/dovecot/conf.d/20-imap.conf
mail_plugins = $mail_plugins antispam

Integrating dspam with lmtp:

protocol lmtp {
# Space separated list of plugins to load (default is global mail_plugins).
mail_plugins = $mail_plugins sieve

Telling sieve to move spam into a Spam folder. Edit /decrypted-mail/ (e.g. for your username and domain):

require ["regex", "fileinto", "imap4flags"];
# Catch mail tagged as Spam, except Spam retrained and delivered to the mailbox
if allof (header :regex "X-DSPAM-Result" "^(Spam|Virus|Bl[ao]cklisted)$",
          not header :contains "X-DSPAM-Reclassified" "Innocent") {
  # Mark as read
  setflag "\\Seen";
  # Move into the Junk folder
  fileinto "Spam";
  # Stop processing here

And then we’ll configure /etc/dovecot/conf.d/90-plugin.conf. Note that, there’s just one plugin {} dict, and this stuff goes inside it.

plugin {
   # Antispam (DSPAM)
   antispam_backend = dspam
   antispam_allow_append_to_spam = YES
   antispam_spam = Spam;Junk
   antispam_trash = trash;Trash
   antispam_signature = X-DSPAM-Signature
   antispam_signature_missing = error
   antispam_dspam_binary = /usr/bin/dspam
   antispam_dspam_args = --user;%u;--deliver=;--source=error
   antispam_dspam_spam = --class=spam
   antispam_dspam_notspam = --class=innocent
   antispam_dspam_result_header = X-DSPAM-Result

Give postfix and dovecot a kick:

service postfix restart
service dovecot restart

Incoming messages should have headers like:

X-Dspam-Result: Innocent
X-Dspam-Processed: Wed Jun 12 21:46:08 2013
X-Dspam-Confidence: 0.9899
X-Dspam-Probability: 0.0000
X-Dspam-Signature: 51b9246071121935811689
X-Dspam-Factors: 27, Received*12+Jun, 0.01000, Received*12+Jun, 0.01000, Received*Postfix+with, 0.01000, Received*with+#+id, 0.01000, Received*, 0.01000, Content-Type*text+plain, 0.01000, Received*Postfix+#+ESMTP, 0.01000, Received*, 0.01000, Received*drew+#+#+#+Jun, 0.01000, Received*Wed+#+Jun, 0.01000, Received*Wed+#+Jun, 0.01000, Received*, 0.01000, Received*Wed+#+#+2013, 0.01000, Received*Wed+#+#+2013, 0.01000, Received*Postfix+#+#+id, 0.01000, Received*, 0.01000, Received*ESMTP+id, 0.01000, Date*12+Jun, 0.01000, Received*for+#+#+#+12, 0.01000, Date*Jun+2013, 0.01000, Received*by+#+Postfix, 0.01000, Received*by+#+with, 0.01000, Received*, 0.01000, Received*by+#+#+#+ESMTP, 0.01000, Date*Wed+#+#+2013, 0.01000, Received*drew+#+#+12, 0.01000, Received*, 0.01000


First we need to install SpamAssassin.

apt-get install spamassassin spamc

Then we also need to create a user for SpamAssassin.

adduser spamd --disabled-login

To successfully configure SpamAssassin, it’s necessary to open and modify the configuration settings.

nano /etc/default/spamassassin

Also, we need to change the ENABLED parameter to enable SpamAssassin daemon.


We need to configure the home and options parameters.

OPTIONS="--create-prefs --max-children 5 --username spamd --helper-home-dir ${SPAMD_HOME} -s ${SPAMD_HOME}spamd.log"

Then we need to specify the PID_File parameter like this:


Finally, we need to specify that SpamAssassin’s rules will be updated automatically.


We need to open /etc/spamassassin/ to set up the anti-spam rules. SpamAssassin will score each mail and if it determines this email is greater than 5.0 on its spam check, then it automatically will be considered spam. You could use the following parameters to configure the anti-spam rules:

nano /etc/spamassassin/

rewrite_header Subject ***** SPAM _SCORE_ *****
report_safe             0
required_score          5.0
use_bayes               1
use_bayes_rules         1
bayes_auto_learn        1
skip_rbl_checks         0
use_razor2              0
use_dcc                 0
use_pyzor               0

We need to change the Postfix /etc/postfix/ file to tell it that each email will be checked with SpamAssassin.

nano /etc/postfix/

smtp      inet  n       -       -       -       -       smtpd
-o content_filter=spamassassin

Finally, it is finished. We need to append the following parameters:

spamassassin unix -     n       n       -       -       pipe
user=spamd argv=/usr/bin/spamc -f -e  
/usr/sbin/sendmail -oi -f ${sender} ${recipient}

It is also necessary to start SpamAssassin and restart Postfix to begin verifying spam from emails.

service spamassassin start
service postfix restart

Done! Mail server with Postfix and Dovecot with MySQL authentication and spam filtering with either dpan or SpamAssassin is ready!

Things to work on:

  • Push: an email system that provides an always-on capability, in which new email is actively transferred (pushed) as it arrives by the mail delivery agent (MDA) (commonly called mail server) to the mail user agent (MUA), also called the email client. Email clients include smartphones and, less strictly, IMAP personal computer mail applications.
  • Full-text search: relying on Java, solr-tomcat & dovecot-solr
  • Webmail: check next tutorial related to simple webmail control (Rainloop).
  • Requiring TLS encryption: makes you non-RFC-compliant, but makse NSA life a bit more difficult
  • Greylisting: This is a cool idea involving providing a “temporary failure” each time a new sender sends a message. Legitimate senders will keep retrying, and after a few minutes you add them to a “good” list. Spammers will give up, and furthermore, their mail messages will never even hit disk. It’s actually really easy configure, just apt-get install postgrey and then add check_policy_service inet: under smtpd_client_restrictions in /etc/postfix/