Introduction
Running your own email server gives you full control over your mail infrastructure. This guide covers the operational side of a Postfix (SMTP) + Dovecot (IMAP/POP3) + PostgreSQL stack โ the configuration files, service management, TLS setup, and debugging techniques you’ll use day-to-day.
Stack: Postfix (send/receive mail) + Dovecot (client access) + PostgreSQL (user/domain storage)
Architecture Overview
Internet โ Postfix (port 25/465) โ Mailbox storage
โ
Client (Thunderbird, Apple Mail) โ Dovecot (port 143/993) โ Mailbox storage
- Postfix handles SMTP: receiving mail from the internet and sending outbound mail
- Dovecot handles IMAP/POP3: letting mail clients read stored mail
- PostgreSQL stores virtual users, domains, and aliases
Key Configuration Files
# Postfix
/etc/postfix/main.cf # main configuration
/etc/postfix/master.cf # service definitions (ports, protocols)
/etc/postfix/virtual # virtual alias maps
/etc/postfix/vmailbox # virtual mailbox maps
# Dovecot
/etc/dovecot/dovecot.conf # main config (includes conf.d/)
/etc/dovecot/conf.d/ # modular config directory
/etc/dovecot/conf.d/10-auth.conf
/etc/dovecot/conf.d/10-mail.conf
/etc/dovecot/conf.d/10-ssl.conf
/etc/dovecot/conf.d/20-imap.conf
Service Management
# Start / stop / restart
sudo service postfix start
sudo service postfix stop
sudo service postfix restart
sudo service postfix reload # reload config without dropping connections
sudo service dovecot start
sudo service dovecot restart
sudo service postgresql start
# Check status
sudo service postfix status
sudo service dovecot status
sudo systemctl status postfix # systemd equivalent
Monitoring and Logs
# Real-time mail log (most useful for debugging)
sudo tail -f /var/log/mail.log
# Error log
sudo tail -f /var/log/mail.err
# systemd journal (alternative)
sudo journalctl -u postfix -f
sudo journalctl -u dovecot -f
sudo journalctl -f # all services
# Check what's listening
sudo ss -ntlp
# Expected ports:
# 25 - SMTP (unencrypted, server-to-server)
# 465 - SMTPS (TLS, client submission)
# 587 - Submission (STARTTLS, client submission)
# 143 - IMAP (unencrypted)
# 993 - IMAPS (TLS)
# 110 - POP3 (unencrypted)
# 995 - POP3S (TLS)
Port Reference
| Port | Protocol | Encryption | Use |
|---|---|---|---|
| 25 | SMTP | None | Server-to-server mail transfer |
| 465 | SMTPS | TLS (implicit) | Client mail submission (legacy) |
| 587 | Submission | STARTTLS | Client mail submission (modern) |
| 143 | IMAP | None | Client mail access |
| 993 | IMAPS | TLS (implicit) | Client mail access (secure) |
| 110 | POP3 | None | Client mail download |
| 995 | POP3S | TLS (implicit) | Client mail download (secure) |
Recommendation: Use ports 993 (IMAPS) and 587 (Submission with STARTTLS) for clients. Never expose 143, 110, or 25 to clients without TLS.
IMAP vs POP3
| Feature | IMAP | POP3 |
|---|---|---|
| Mail storage | Server (synced) | Downloaded to client |
| Multiple devices | Yes โ all devices see same state | No โ mail removed from server |
| Offline access | Partial (cached) | Full (downloaded) |
| Server storage | Higher | Lower |
| Recommended | Yes | Only for single-device, low-storage scenarios |
Use IMAP. POP3 is only useful if you have very limited server storage and access mail from a single device.
Testing SMTP Connectivity
# Test SMTP connection with netcat
nc mail.example.com 25
# You should see the SMTP banner:
# 220 mail.example.com ESMTP Postfix
# Manual SMTP session
EHLO testclient.example.com
# Server responds with capabilities:
# 250-mail.example.com
# 250-PIPELINING
# 250-SIZE 10240000
# 250-STARTTLS
# 250-AUTH PLAIN LOGIN
# 250 8BITMIME
# Test with openssl (for TLS)
openssl s_client -connect mail.example.com:465
openssl s_client -starttls smtp -connect mail.example.com:587
Sending Test Email from Command Line
# Using mail command
echo "Test body" | mail -s "Test subject" [email protected]
# Using sendmail directly
sendmail -v [email protected] << EOF
Subject: Test
From: [email protected]
Test message body
EOF
# Using swaks (Swiss Army Knife for SMTP โ install: apt install swaks)
swaks --to [email protected] --from [email protected] --server localhost
swaks --to [email protected] --server mail.example.com --port 587 \
--auth LOGIN --auth-user [email protected] --auth-password secret \
--tls
Enabling TLS with Let’s Encrypt
Postfix TLS Configuration
sudo nano /etc/postfix/main.cf
# TLS for incoming connections (smtpd = server)
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.example.com/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/mail.example.com/privkey.pem
smtpd_use_tls = yes
smtpd_tls_security_level = may # offer TLS but don't require it for port 25
smtpd_tls_loglevel = 1
smtpd_tls_received_header = yes
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
# TLS for outgoing connections (smtp = client)
smtp_use_tls = yes
smtp_tls_security_level = may
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
# Modern TLS settings
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
Dovecot TLS Configuration
sudo nano /etc/dovecot/conf.d/10-ssl.conf
# Require TLS for all connections
ssl = required
# Certificate files
ssl_cert = </etc/letsencrypt/live/mail.example.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.example.com/privkey.pem
# Modern TLS settings
ssl_min_protocol = TLSv1.2
ssl_cipher_list = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
ssl_prefer_server_ciphers = yes
Reload After TLS Changes
sudo service postfix reload
sudo service dovecot reload
# Verify TLS is working
openssl s_client -connect mail.example.com:993 -quiet
Renewing Let’s Encrypt Certificates
# Test renewal
sudo certbot renew --dry-run
# Actual renewal (runs automatically via cron/systemd timer)
sudo certbot renew
# Reload mail services after renewal
# Add to /etc/letsencrypt/renewal-hooks/deploy/reload-mail.sh:
#!/bin/bash
service postfix reload
service dovecot reload
Common Postfix main.cf Settings
# Identity
myhostname = mail.example.com
mydomain = example.com
myorigin = $mydomain
# Networks allowed to relay
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
# Virtual domains and mailboxes
virtual_mailbox_domains = pgsql:/etc/postfix/pgsql-virtual-mailbox-domains.cf
virtual_mailbox_maps = pgsql:/etc/postfix/pgsql-virtual-mailbox-maps.cf
virtual_alias_maps = pgsql:/etc/postfix/pgsql-virtual-alias-maps.cf
# Mailbox storage
virtual_transport = lmtp:unix:private/dovecot-lmtp
# Size limits
message_size_limit = 52428800 # 50MB max message size
mailbox_size_limit = 0 # no mailbox size limit
# Anti-spam
smtpd_recipient_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_unauth_destination
Debugging Common Issues
# Mail not being delivered โ check the queue
mailq # show mail queue
postqueue -p # same
postqueue -f # flush queue (retry delivery)
postsuper -d ALL # delete all queued mail (careful!)
# Check if a specific domain resolves
dig MX example.com
dig A mail.example.com
# Test authentication
doveadm auth test [email protected] password
# Check Postfix config for errors
postfix check
postconf -n # show non-default settings
# Verify DKIM/SPF/DMARC (use external tools)
# https://mxtoolbox.com/
# https://mail-tester.com/
Resources
- Postfix Documentation
- Dovecot Documentation
- ISPmail Tutorial (Debian/Ubuntu)
- Mail-Tester โ test your mail server score
- MXToolbox โ DNS and mail diagnostics
Comments