Intro

Based on this guide (web archive), which is old and doesn’t work with recent Dovecot.

Basically, it’s a partial copy of the above guide with a couple of updates. Please read original guide for details/explanations/comments.

You’ll need Arch linux server, domain name and ISP which can provide you with reverse DNS (just send them an e-mail and ask).

Before sending any e-mails to real external servers, try e-mail tester services first to avoid being included in spam lists even before you’ve got everything working. I’ve used https://www.mail-tester.com/.

After initial setup or futher configuration changes, make backups: at least Dovecot is notorious for breaking backward compatibility. If you have a configs backup, you can roll back Dovecot version from /var/cache/pacman/pkg/, and then restore your config. At least you’ll have working e-mail server while you deal with the issue.

Certificates

Unlike the original guide with a self-signed certificate, I use Let's Encrypt.

Set up automatic renewal of your certificate, otherwise it will definitely expire when you’re away from your computer and waiting for an important email.

https://wiki.archlinux.org/title/Certbot

Dovecot

https://wiki.archlinux.org/title/Dovecot

Installation

pacman -S dovecot

Configuration

Mail location and type

doveconf -n mail_path namespace inbox

Output:

mail_path = ~/Maildir
namespace inbox {
  inbox = yes
  separator = /
  mailbox Drafts {
    special_use = "\\Drafts"
  }
  mailbox Junk {
    special_use = "\\Junk"
  }
  mailbox Trash {
    special_use = "\\Trash"
  }
  mailbox Sent {
    special_use = "\\Sent"
  }
  mailbox "Sent Messages" {
    special_use = "\\Sent"
  }
}

Add dovecot to mail group

usermod -aG mail dovecot
id dovecot

Output:

uid=76(dovecot) gid=76(dovecot) groups=76(dovecot),12(mail)

SSL certificates

SSL

doveconf ssl ssl_server_cert_file ssl_server_key_file ssl_server_dh_file

Output:

ssl = yes
ssl_server_cert_file = /etc/letsencrypt/live/kostovsky.me/fullchain.pem
ssl_server_key_file = /etc/letsencrypt/live/kostovsky.me/privkey.pem
ssl_server_dh_file = /etc/dovecot/dh.pem

Authentication

doveconf -n userdb passdb

Output (section names are random, dovecot started requiring section names starting from some version):

userdb fuck_dovecot {
  driver = passwd
}
passdb fuck_dovecot {
  driver = pam
}
cat /etc/pam.d/dovecot

Output:

#%PAM-1.0
auth include system-auth
account include system-auth
session include system-auth
password include system-auth

Create system user

useradd nikita -m
passwd nikita

Final conf

doveconf -n

Output:

## 2.4.4 (8b687aa65c): /etc/dovecot/dovecot.conf
## OS: Linux 7.1.5-arch1-2 x86_64  
## Hostname: home
## 29 default setting changes since version 2.4.1
dovecot_config_version = 2.4.1
auth_mechanisms = plain
dovecot_storage_version = 2.4.1
mail_driver = maildir
mail_path = ~/Maildir
protocols {
  imap = yes
  lmtp = yes
}
passdb fuck_dovecot {
  driver = pam
}
userdb fuck_dovecot {
  driver = passwd
}
service director {
  listen = * ::
  unix_listener login/director {
  }
  fifo_listener login/proxy-notify {
  }
  unix_listener director-userdb {
  }
}
service imap-login {
  inet_listener imap {
  }
  inet_listener imaps {
  }
}
service pop3-login {
  inet_listener pop3 {
  }
  inet_listener pop3s {
  }
}
service submission-login {
  inet_listener submission {
  }
  inet_listener submissions {
  }
}
namespace inbox {
  inbox = yes
  separator = /
  mailbox Drafts {
    special_use = "\\Drafts"
  }
  mailbox Junk {
    special_use = "\\Junk"
  }
  mailbox Trash {
    special_use = "\\Trash"
  }
  mailbox Sent {
    special_use = "\\Sent"
  }
  mailbox "Sent Messages" {
    special_use = "\\Sent"
  }
}
service lmtp {
  unix_listener lmtp {
  }
}
service imap {
}
service pop3 {
}
service submission {
}
service auth {
  unix_listener /var/spool/postfix/private/auth {
    group = postfix
    mode = 0666
    user = postfix
  }
}
service auth-worker {
}
service dict {
  unix_listener dict {
  }
}
ssl_server {
  cert_file = /etc/letsencrypt/live/kostovsky.me/fullchain.pem
  dh_file = /etc/dovecot/dh.pem
  key_file = /etc/letsencrypt/live/kostovsky.me/privkey.pem
}

Service

systemctl enable --now dovecot.service

Please check original guide if you use ufw.

Testing tools

Postfix

https://wiki.archlinux.org/title/Postfix

Installation

pacman -S postfix

Directories

postconf -n | grep -E "directory\s"

Output:

command_directory = /usr/bin
daemon_directory = /usr/lib/postfix/bin
data_directory = /var/lib/postfix
html_directory = no
manpage_directory = /usr/share/man
meta_directory = /etc/postfix
queue_directory = /var/spool/postfix
readme_directory = /usr/share/doc/postfix
sample_directory = /etc/postfix
shlib_directory = /usr/lib/postfix

Domain

postconf -n | grep ^my

Output:

mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain, mail.$mydomain
mydomain = kostovsky.me
myhostname = kostovsky.me
myorigin = $mydomain

Aliases

postconf -n | grep -E "^alias|newaliases"

Output:

alias_database = $alias_maps
alias_maps = lmdb:/etc/postfix/aliases
newaliases_path = /usr/bin/newaliases

Catch-all email

postconf -n luser_relay local_recipient_maps

Output:

local_recipient_maps =

Secure email with TLS

Receiving:

postconf -n | grep smtpd_ | grep tls

Output:

smtpd_sasl_tls_security_options = noanonymous
smtpd_tls_auth_only = yes
smtpd_tls_cert_file = /etc/letsencrypt/live/kostovsky.me/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/kostovsky.me/privkey.pem
smtpd_use_tls = yes

Sending:

postconf -n | grep smtp_ | grep tls

Output:

smtp_tls_security_level = may

Authentication/authorization

Postfix auth config:

postconf -n | grep ^smtpd_sasl

Output:

smtpd_sasl_auth_enable = yes
smtpd_sasl_local_domain = $mydomain
smtpd_sasl_path = private/auth
smtpd_sasl_security_options = noanonymous, noplaintext
smtpd_sasl_tls_security_options = noanonymous
smtpd_sasl_type = dovecot

Dovecot auth integration

doveconf -n service/auth

Output:

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

Relay and restrictions

postconf -n | grep -E "helo|relay"

Output:

smtpd_helo_required = yes
smtpd_helo_restrictions = reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname
smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination

Mail submission

postconf -M submission

Output:

submission inet  n       -       n       -       -       smtpd -o syslog_name=postfix/submission -o smtpd_tls_security_level=encrypt -o smtpd_sasl_auth_enable=yes -o smtpd_tls_auth_only=yes -o smtpd_reject_unlisted_recipient=no -o smtpd_relay_restrictions= -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject -o milter_macro_daemon_name=ORIGINATING

DNS TXT record

drill -Q kostovsky.me TXT
drill -Q smtp.kostovsky.me TXT

Output:

"v=spf1 a mx ip4:195.234.21.24 ~all"
"v=spf1 a mx ip4:195.234.21.24 ~all"

Reverse DNS record

drill -Q 195.234.21.24 -x

Output:

kostovsky.me.

Final conf

postconf -n

Output:

alias_database = $alias_maps
alias_maps = lmdb:/etc/postfix/aliases
command_directory = /usr/bin
compatibility_level = 3.11
daemon_directory = /usr/lib/postfix/bin
data_directory = /var/lib/postfix
debug_peer_level = 2
debugger_command = PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin ddd $daemon_directory/$process_name $process_id & sleep 5
home_mailbox = Maildir/
html_directory = no
inet_protocols = ipv4
local_recipient_maps =
mail_owner = postfix
mailbox_size_limit = 512000000
mailq_path = /usr/bin/mailq
manpage_directory = /usr/share/man
message_size_limit = 512000000
meta_directory = /etc/postfix
milter_default_action = accept
mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain, mail.$mydomain
mydomain = kostovsky.me
myhostname = kostovsky.me
mynetworks = 10.66.66.0/24
myorigin = $mydomain
newaliases_path = /usr/bin/newaliases
non_smtpd_milters = $smtpd_milters
queue_directory = /var/spool/postfix
readme_directory = /usr/share/doc/postfix
sample_directory = /etc/postfix
sendmail_path = /usr/bin/sendmail
setgid_group = postdrop
shlib_directory = /usr/lib/postfix
smtp_header_checks = regexp:/etc/postfix/smtp_header_checks
smtp_tls_security_level = may
smtpd_banner = exim
smtpd_helo_required = yes
smtpd_helo_restrictions = reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname
smtpd_milters = inet:localhost:8891, inet:localhost:8893
smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
smtpd_sasl_auth_enable = yes
smtpd_sasl_local_domain = $mydomain
smtpd_sasl_path = private/auth
smtpd_sasl_security_options = noanonymous, noplaintext
smtpd_sasl_tls_security_options = noanonymous
smtpd_sasl_type = dovecot
smtpd_tls_auth_only = yes
smtpd_tls_cert_file = /etc/letsencrypt/live/kostovsky.me/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/kostovsky.me/privkey.pem
smtpd_use_tls = yes
unknown_local_recipient_reject_code = 550

Service

systemctl enable --now postfix.service

Please check original guide if you use ufw.

Testing tools

For all geeks out there you can use openssl to do basic SMTP testing.

openssl s_client -connect kostovsky.me:25 -starttls smtp

DomainKeys Identified Mail - DKIM

Installation

https://wiki.archlinux.org/title/OpenDKIM

pacman -S opendkim

Configuration

Minimal config

grep -v -e '^#' -e '^[[:space:]]*$' /etc/opendkim/opendkim.conf

Output:

Canonicalization	  relaxed/simple
Domain			  kostovsky.me
KeyFile			  /etc/opendkim/rig.private
Selector		  rig
Socket                    inet:8891@localhost
Syslog			  Yes
UserID                    opendkim:postfix

Generate key file

Generate rig.private and rig.txt files.

opendkim-genkey --restrict --selector rig --domain kostovsky.me --directory /etc/opendkim

Postfix integration

postconf -n | grep milter

Output:

milter_default_action = accept
non_smtpd_milters = $smtpd_milters
smtpd_milters = inet:localhost:8891, inet:localhost:8893

Socket communication via inet:localhost:8891.

DNS TXT record

cat /etc/opendkim/rig.txt

Output:

rig._domainkey	IN	TXT	( "v=DKIM1; k=rsa; s=email; "
          "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA8dfa8T7Hhn5/nWVivUpeBEs339fa9bCUHN7tqWxchzy5vBw/8Z3/9Pp8XIXo/0GnpOCu0Fh4P27tMnIdH5N4Sy3E/XcNHL7cRAB7gqKjEMuT2S49L7j3g19Dns7qN8rbArC7Krzq5mdHPHk1EqGsytGlv9CMD5gegpUsM7wd3QIDAQAB" )  ; ----- DKIM key rig for kostovsky.me
drill -Q rig._domainkey.kostovsky.me TXT

Output:

"v=DKIM1; k=rsa; s=email; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA8dfa8T7Hhn5/nWVivUpeBEs339fa9bCUHN7tqWxchzy5vBw/8Z3/9Pp8XIXo/0GnpOCu0Fh4P27tMnIdH5N4Sy3E/XcNHL7cRAB7gqKjEMuT2S49L7j3g19Dns7qN8rbArC7Krzq5mdHPHk1EqGsytGlv9CMD5gegpUsM7wd3QIDAQAB"

Service

systemctl enable --now opendkim.service

Testing tools

Domain-based Message Authentication, Reporting and Conformance - DMARC

https://wiki.archlinux.org/title/OpenDMARC

Installation

pacman -S opendmarc

Configuration

Minimal config

grep -vE "^#|^\s*$" /etc/opendmarc/opendmarc.conf

Output:

AuthservID HOSTNAME
IgnoreAuthenticatedClients true
Socket inet:8893@localhost
SPFSelfValidate true
UMask 002

Postfix integration

postconf -n | grep milter

Output:

milter_default_action = accept
non_smtpd_milters = $smtpd_milters
smtpd_milters = inet:localhost:8891, inet:localhost:8893

Socket communication via inet:localhost:8893.

DNS TXT record

drill -Q _dmarc.kostovsky.me TXT

Output:

"v=DMARC1; p=quarantine; rua=mailto:postmaster@kostovsky.me; ruf=mailto:forensic@kostovsky.me; adkim=s; aspf=s; fo=1; pct=25"

Service

systemctl enable --now opendmarc.service

Testing tools

Thunderbird config

Outgoing server

“Account Settings”->“Edit outgoing server…”

  • Server Name: kostovsky.me
  • Port: 587
  • Connection security: STARTTLS
  • Authentication method: Normal password
  • User Name: nikita

Incoming server

“Server settings” section

  • Server Type: IMAP Mail Server
  • Server Name: kostovsky.me
  • Port: 993
  • User Name: nikita
  • Connection security: SSL/TLS
  • Authentication method: Normal password