From “How hard can it be?” to “Oh, that’s why people use Gmail” – A tale of SMTP, DKIM, and debugging
Introduction
After successfully building the web server foundation in Part 1, I was feeling confident. “Email can’t be that hard,” I thought. “It’s just SMTP and IMAP, right?”
Narrator: It was, in fact, that hard.
This is the story of building a production-ready mail server, complete with authentication, encryption, spam filtering, virus scanning, and enough troubleshooting to humble any system administrator. But in the end, when that first email arrived in my inbox with a green checkmark and perfect DKIM signature, it was absolutely worth it.
The Email Vision
Goal: Self-hosted email server with:
- Full SMTP and IMAP support
- Web-based email account management
- DKIM signing for email authentication
- SPF and DMARC for sender validation
- Spam filtering (SpamAssassin)
- Virus scanning (ClamAV)
- Fail2Ban for intrusion prevention
- SSL/TLS encryption everywhere
Reality Check: This took considerably longer than the web server.
Part 1: Choosing the Stack
The Research Phase
I evaluated several options:
- Postfix + Dovecot (manual) – Ultimate flexibility, maximum pain
- Mail-in-a-Box – Too automated, limited customization
- iRedMail – Good, but complex setup
- docker-mailserver – Perfect balance of automation and control
Winner: docker-mailserver (DMS)
Why?
- Well-maintained and documented
- Includes everything: Postfix, Dovecot, OpenDKIM, SpamAssassin, ClamAV, Fail2Ban
- Integrates with existing Docker infrastructure
- Active community support
- Can use PostfixAdmin for web management
The Architecture
Internet (Port 25/587/465/993)
↓
Nginx (SSL termination for HTTPS, pass-through for mail)
↓
docker-mailserver (SMTP/IMAP)
↓
MySQL (PostfixAdmin database)
↓
PostfixAdmin (Web interface)
Part 2: Initial Configuration
Step 1: DNS Records
Before even starting the containers, I needed proper DNS records. This is critical – email won’t work without them.
# A Records
mail.blueelysium.net A [SERVER_IP]
mailadmin.blueelysium.net A [SERVER_IP]
# MX Record (highest priority)
blueelysium.net MX 10 mail.blueelysium.net.
# SPF Record (authorize this server to send email)
blueelysium.net TXT "v=spf1 mx ~all"
# DMARC Record (email authentication policy)
_dmarc.blueelysium.net TXT "v=DMARC1; p=quarantine; rua=mailto:postmaster@blueelysium.net"
Note: DKIM record comes later after generating keys.
Step 2: Docker Compose Configuration
Adding the mail server to docker-compose.yml:
mailserver:
image: ghcr.io/docker-mailserver/docker-mailserver:latest
container_name: mailserver
hostname: mail
domainname: blueelysium.net
ports:
- "25:25" # SMTP
- "143:143" # IMAP
- "465:465" # SMTPS
- "587:587" # Submission
- "993:993" # IMAPS
volumes:
- ./docker-data/dms/mail-data:/var/mail
- ./docker-data/dms/mail-state:/var/mail-state
- ./docker-data/dms/mail-logs:/var/log/mail
- ./docker-data/dms/config:/tmp/docker-mailserver
- ./docker-data/certbot/conf:/etc/letsencrypt:ro
environment:
- ENABLE_SPAMASSASSIN=1
- ENABLE_CLAMAV=1
- ENABLE_FAIL2BAN=1
- ENABLE_POSTGREY=0
- ENABLE_AMAVIS=1
- ONE_DIR=1
- DMS_DEBUG=0
- PERMIT_DOCKER=network
- SSL_TYPE=letsencrypt
- SSL_DOMAIN=mail.blueelysium.net
- POSTFIX_MAILBOX_SIZE_LIMIT=0
- POSTFIX_MESSAGE_SIZE_LIMIT=50000000
Key Decisions:
- Shared SSL certificates with web server (Let’s Encrypt)
- All security features enabled from day one
- MySQL backend for virtual mailboxes (integration with PostfixAdmin)
- One directory mode for easier backups
Step 3: PostfixAdmin Integration
PostfixAdmin provides a web interface for managing email accounts, domains, and aliases.
postfixadmin:
image: postfixadmin/postfixadmin:latest
container_name: postfixadmin
ports:
- "8080:80"
environment:
POSTFIXADMIN_DB_TYPE: mysqli
POSTFIXADMIN_DB_HOST: db
POSTFIXADMIN_DB_NAME: ${MYSQL_PFBE_DATABASE}
POSTFIXADMIN_DB_USER: ${MYSQL_PFBE_USER}
POSTFIXADMIN_DB_PASSWORD: ${MYSQL_PFBE_PASSWORD}
POSTFIXADMIN_SETUP_PASSWORD: ${POSTFIXADMIN_SETUP_PASS}
depends_on:
- db
Initial Setup:
- Access
https://mailadmin.blueelysium.net:8080/setup.php - Create setup password hash
- Create superadmin account
- Add domain:
blueelysium.net - Create first mailbox:
frank@blueelysium.net
Part 3: The MySQL Integration Challenge
The Problem
docker-mailserver needs to query MySQL for:
- Virtual domains
- Virtual mailboxes
- Virtual aliases
- User authentication
But it doesn’t include MySQL client libraries by default!
The Solution: user-patches.sh
I created a custom startup script that runs after DMS initializes:
#!/bin/bash
# docker-data/dms/config/user-patches.sh
echo "Installing MySQL client libraries..."
apt-get update
apt-get install -y postfix-mysql dovecot-mysql
echo "Configuring Postfix virtual domains..."
cat > /etc/postfix/mysql-virtual-mailbox-domains.cf << 'EOF'
user = ${MYSQL_PFBE_USER}
password = ${MYSQL_PFBE_PASSWORD}
hosts = db
dbname = ${MYSQL_PFBE_DATABASE}
query = SELECT domain FROM domain WHERE domain='%s' AND active='1'
EOF
echo "Configuring Postfix virtual mailboxes..."
cat > /etc/postfix/mysql-virtual-mailbox-maps.cf << 'EOF'
user = ${MYSQL_PFBE_USER}
password = ${MYSQL_PFBE_PASSWORD}
hosts = db
dbname = ${MYSQL_PFBE_DATABASE}
query = SELECT maildir FROM mailbox WHERE username='%s' AND active='1'
EOF
echo "Configuring Postfix virtual aliases..."
cat > /etc/postfix/mysql-virtual-alias-maps.cf << 'EOF'
user = ${MYSQL_PFBE_USER}
password = ${MYSQL_PFBE_PASSWORD}
hosts = db
dbname = ${MYSQL_PFBE_DATABASE}
query = SELECT goto FROM alias WHERE address='%s' AND active='1'
EOF
# Update Postfix main.cf
postconf -e "virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf"
postconf -e "virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf"
postconf -e "virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf"
# Reload Postfix
postfix reload
echo "MySQL integration complete!"
Critical Learning: This script runs every time the container starts, ensuring configuration persists.
Part 4: DKIM Configuration Drama
What is DKIM?
DKIM (DomainKeys Identified Mail) cryptographically signs outgoing emails to prove they’re legitimate. Without it, emails often go to spam.
Generating DKIM Keys
docker exec mailserver setup config dkim domain blueelysium.net
This creates:
- Private key:
/tmp/docker-mailserver/opendkim/keys/blueelysium.net/default.private - Public key:
/tmp/docker-mailserver/opendkim/keys/blueelysium.net/default.txt
The First DKIM Crisis
Symptom: Emails weren’t getting DKIM signatures
Error in logs:
opendkim[212]: error loading key 'mail._domainkey.blueelysium.net'
Investigation:
docker exec mailserver cat /etc/opendkim/KeyTable
mail._domainkey.blueelysium.net blueelysium.net:mail:/etc/opendkim/keys/blueelysium.net/mail.private
docker exec mailserver ls /etc/opendkim/keys/blueelysium.net/
default.private default.txt
The Problem: The configuration was looking for selector mail, but the actual key was named default!
The Fix:
Updated KeyTable:
docker exec mailserver bash -c 'echo "default._domainkey.blueelysium.net blueelysium.net:default:/etc/opendkim/keys/blueelysium.net/default.private" > /etc/opendkim/KeyTable'
Updated SigningTable:
docker exec mailserver bash -c 'echo "*@blueelysium.net default._domainkey.blueelysium.net" > /etc/opendkim/SigningTable'
Making it Persistent:
Created permanent config files in docker-data/dms/config/opendkim/:
sudo bash -c 'cat > docker-data/dms/config/opendkim/KeyTable << "EOF"
default._domainkey.blueelysium.net blueelysium.net:default:/etc/opendkim/keys/blueelysium.net/default.private
EOF'
sudo bash -c 'cat > docker-data/dms/config/opendkim/SigningTable << "EOF"
*@blueelysium.net default._domainkey.blueelysium.net
EOF'
Publishing DKIM DNS Record
docker exec mailserver cat /etc/opendkim/keys/blueelysium.net/default.txt
Output:
default._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; "
"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsks63VLLrZy0EJcDn5mp..."
"...rest of very long key..." )
Added to DNS as single TXT record:
default._domainkey.blueelysium.net TXT "v=DKIM1; h=sha256; k=rsa; p=MIIBIjANBgkq...COMPLETE_KEY...AQAB"
Critical: Must include the ENTIRE public key in one record. I initially had a truncated version that caused validation failures.
Verification:
docker exec mailserver opendkim-testkey -d blueelysium.net -s default -vvv
Output: key OK
🎉 First Success: Sent test email, received with DKIM signature verified!
Part 5: The Desktop Connection Mystery
The Problem
Symptoms:
- Laptop (different VLAN): ✅ Could connect to mail server
- Desktop (different VLAN): ❌ Connection timeout on ports 993, 465, 587
- Same desktop: ✅ Could access website (ports 80, 443)
What I Tried:
- Firewall rules on UniFi – Created allow rules, no change
- UFW on server – All ports already allowed
- IPS/Threat Management – Temporarily disabled, no change
- Windows Firewall – Disabled on desktop, still timing out
- Docker iptables – Verified forwarding rules correct
The Investigation
Used tcpdump on the server to watch network traffic:
# Monitor port 993 traffic
sudo tcpdump -i any port 993 -nn
# Send test connection from desktop
# Desktop: nc -v mail.blueelysium.net 993
What I Saw:
SYN packet from 10.0.6.91:52345 → server:993 ← Desktop sending request
Packet forwarded to Docker container ← Reached the server
No SYN-ACK response ← Container not responding!
The Smoking Gun: Packets were reaching the server and Docker container, but the container wasn’t responding. This pointed to something inside the mailserver container blocking the connection.
The Fail2Ban Revelation
On a hunch, I checked Fail2Ban status:
docker exec mailserver fail2ban-client status dovecot
Output:
Status for the jail: dovecot
|- Filter
| |- Currently failed: 1
| |- Total failed: 2
| `- File list: /var/log/mail.log
`- Actions
|- Currently banned: 1
|- Total banned: 1
`- Banned IP list: <My LAN IP>
BINGO! My desktop IP was banned by Fail2Ban!
Why? I had been testing authentication multiple times during setup, triggering Fail2Ban’s “too many failed login attempts” protection.
The Fix: Part 1 – Unban
docker exec mailserver fail2ban-client set dovecot unbanip <My LAN IP>
✅ Desktop immediately connected!
Part 6: The Dovecot Authentication Puzzle
The Next Challenge
Desktop could now connect, but authentication failed:
Outlook Error: “Cannot connect to server. Verify username and password.”
Server Logs:
dovecot: auth-worker: Invalid password in passdb: Not a valid MD5-CRYPT or PLAIN-MD5 password
Understanding the Problem
PostfixAdmin stores passwords as SHA512-CRYPT hashes:
$6$rounds=5000$randomsalt$hashedpassword...
Dovecot was trying to read them as PLAIN-MD5, which didn’t match.
The Solution
Modified Dovecot’s SQL configuration to handle the hash format:
docker exec mailserver bash -c "cat > /etc/dovecot/dovecot-sql.conf.ext << 'EOF'
driver = mysql
connect = host=db dbname=pfbe user=pfuser password=SecurePassword
default_pass_scheme = CRYPT
password_query = SELECT username AS user, CONCAT('{CRYPT}', password) AS password FROM mailbox WHERE username='%u' AND active='1'
user_query = SELECT maildir AS home, 5000 AS uid, 5000 AS gid FROM mailbox WHERE username='%u' AND active='1'
EOF"
Key Changes:
default_pass_scheme = CRYPT– Tells Dovecot to expect crypt() formatCONCAT('{CRYPT}', password)– Prefixes hash with {CRYPT} so Dovecot knows how to handle it
Added to user-patches.sh for persistence.
Restarted mailserver:
docker restart mailserver
✅ Authentication successful! Could now send and receive emails!
Part 7: Client Configuration
Outlook Configuration
IMAP Settings (Receiving):
- Server: mail.blueelysium.net
- Port: 993
- Encryption: SSL/TLS
- Username: frank@blueelysium.net
- Password: (from PostfixAdmin)
SMTP Settings (Sending):
- Server: mail.blueelysium.net
- Port: 587
- Encryption: STARTTLS
- Authentication: Required
- Username: frank@blueelysium.net
- Password: (same as above)
Alternative SMTP:
- Port: 465 with SSL/TLS also works
Testing
Send test email:
echo "Test email body" | mail -s "Test Subject" frank@blueelysium.net
Check mail queue:
docker exec mailserver postqueue -p
View logs:
docker logs mailserver --tail 50
Check mailbox:
docker exec mailserver ls -la /var/mail/blueelysium.net/frank/cur/
Part 8: Fine-Tuning & Optimization
SpamAssassin Training
# Train on spam
docker exec mailserver sa-learn --spam /var/mail/blueelysium.net/frank/.Junk/cur/*
# Train on ham (legitimate email)
docker exec mailserver sa-learn --ham /var/mail/blueelysium.net/frank/cur/*
# Check stats
docker exec mailserver sa-learn --dump magic
ClamAV Database Updates
ClamAV updates virus definitions automatically, but I verified:
docker exec mailserver freshclam
Monitoring Configuration
Created simple monitoring script:
#!/bin/bash
# check-mail.sh
echo "=== Mail Server Status ==="
docker exec mailserver supervisorctl status
echo -e "\n=== Recent Auth Attempts ==="
docker logs mailserver | grep "auth" | tail -10
echo -e "\n=== Mail Queue ==="
docker exec mailserver postqueue -p
echo -e "\n=== Fail2Ban Status ==="
docker exec mailserver fail2ban-client status dovecot
docker exec mailserver fail2ban-client status postfix
SSL Certificate Sharing
The mail server uses the same Let’s Encrypt certificates as the web server:
volumes:
- ./docker-data/certbot/conf:/etc/letsencrypt:ro
Important: After certificate renewal, restart mail server:
docker compose restart mailserver
Added to renewal script for automation.
Part 9: Testing & Validation
Email Deliverability Testing
Tools Used:
- mail-tester.com – Comprehensive deliverability check
- MXToolbox – DNS and configuration validation
- Gmail – Send test email, check headers
Initial Score: 8.5/10
Issues Found:
- Reverse DNS (PTR record) not set – Contacted ISP to set PTR record
- DMARC record could be stronger – Updated to
p=quarantine
Final Score: 10/10 🎉
DKIM Validation
Sent email to Gmail, viewed headers:
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=blueelysium.net;
s=default; t=1700000000;
bh=abc123...;
h=Date:From:To:Subject:From;
b=xyz789...
dkim=pass (2048-bit key) header.d=blueelysium.net header.i=@blueelysium.net
✅ DKIM signing and verification working perfectly!
SPF Validation
Received-SPF: pass (google.com: domain of frank@blueelysium.net designates [SERVER_IP] as permitted sender)
✅ SPF validation passing!
Load Testing
Sent 100 test emails to verify performance:
for i in {1..100}; do
echo "Test email $i" | mail -s "Load Test $i" frank@blueelysium.net
done
Results:
- All 100 emails delivered
- Average processing time: <1 second per email
- No performance degradation
- CPU usage stayed under 30%
Part 10: Lessons Learned (The Hard Way)
1. DNS is Everything
Email is heavily dependent on DNS.
Lesson: Set up ALL DNS records before starting, and verify with multiple tools.
2. Fail2Ban is Aggressive
Fail2Ban protected the server beautifully, but also banned me multiple times during testing.
Lesson: Always whitelist internal networks before testing authentication.
3. DKIM Selector Names Matter
The mismatch between mail and default cost me hours of debugging.
Lesson: Check DKIM KeyTable and SigningTable match actual key filenames.
4. Password Hashing is Tricky
PostfixAdmin’s SHA512-CRYPT format required specific Dovecot configuration.
Lesson: Always specify default_pass_scheme and test authentication immediately.
5. Logs are Your Friend
Every single issue was solved by reading logs carefully:
docker logs mailserver | tail -100
docker exec mailserver grep "error" /var/log/mail.log
Lesson: When stuck, read the logs. Then read them again.
6. Network Troubleshooting Tools
tcpdump was instrumental in discovering the Fail2Ban issue:
sudo tcpdump -i any port 993 -nn
Lesson: Learn basic network debugging tools before starting.
7. Documentation Saves Time
Every fix I documented helped when similar issues appeared later.
Lesson: Document everything, especially working configurations.
Part 11: The Backup System
With email working, data protection became critical. I built a comprehensive backup system:
What Gets Backed Up:
- All mailboxes (
/var/mail/) - Mail server state and configuration
- PostfixAdmin database
- DKIM keys
- SSL certificates
Backup Schedule:
- Weekly full backups (Sunday 2:00 AM)
- 4-week retention
- SHA256 integrity verification
Backup Script Highlights:
# Backup mail data
tar czf mail_data.tar.gz -C /var/mail .
# Backup mail state
tar czf mail_state.tar.gz -C /var/mail-state .
# Backup DKIM keys
tar czf dkim_keys.tar.gz -C /tmp/docker-mailserver/opendkim .
# Backup configuration
tar czf config.tar.gz docker-data/dms/config/
Restore Testing:
- Monthly restore verification
- Complete disaster recovery procedures documented
Full documentation: docs/BACKUP-RESTORE-GUIDE.md
Part 12: Current Status & Performance
Mail Server Stats (After 1 Month)
Reliability:
- Uptime: 99.9%
- Email delivery success: 100%
- Zero deliverability issues
- No bounces or rejections
Security:
- Fail2Ban: 15 IPs banned (external attackers)
- SpamAssassin: 23 spam emails caught
- ClamAV: 0 viruses detected (thankfully!)
- SSL/TLS: All connections encrypted
Performance:
- Email delivery: <1 second
- IMAP sync: Nearly instant
- Resource usage: 200MB RAM, <5% CPU
- Storage: 82KB mail data (mostly test emails)
What’s Working Brilliantly
✅ DKIM signing – 100% verification rate
✅ SPF validation – All emails pass
✅ SSL/TLS encryption – A+ rating
✅ Spam filtering – Excellent catch rate
✅ Fail2Ban protection – Blocking attacks automatically
✅ Automated backups – Weekly, verified backups
✅ Client compatibility – Works with Outlook, Thunderbird, mobile
What Could Be Better
🔸 Monitoring dashboard – Currently manual checks
🔸 Email retention policies – Not automated yet
🔸 Redundancy – Single server, no failover
🔸 Push notifications – Would need additional setup
Part 13: The Bigger Picture
Why Self-Host Email?
Advantages:
- Complete Privacy – My data, my server
- No Limits – No storage quotas or message limits
- Custom Domains – Professional email addresses
- Learning Experience – Deep understanding of email infrastructure
- Cost – Free after initial setup (besides server costs)
Disadvantages:
- Complexity – Requires technical knowledge
- Responsibility – You’re the sysadmin
- Deliverability – ISPs may be suspicious of self-hosted
- Maintenance – Updates and monitoring required
- No Support – You’re on your own for troubleshooting
My Verdict: Worth it for the learning and control, but not for everyone.
Skills Gained
Through this project, I learned:
- Email Protocols: SMTP, IMAP, POP3 in depth
- Authentication: DKIM, SPF, DMARC implementation
- Security: Fail2Ban, SSL/TLS, encryption
- Networking: DNS, iptables, Docker networking, tcpdump
- Troubleshooting: Log analysis, packet inspection, systematic debugging
- Database Integration: MySQL backend for virtual users
- Automation: Backup scripts, monitoring, maintenance
- Documentation: Writing technical documentation
These skills translate directly to professional DevOps/SysAdmin work.
Part 14: Common Issues & Solutions
Issue 1: Email Goes to Spam
Symptoms: Emails arrive in recipient’s spam folder
Solutions:
- Check DKIM signature:
opendkim-testkey - Verify SPF record:
dig blueelysium.net TXT - Add DMARC record
- Set up reverse DNS (PTR record)
- Test with mail-tester.com
- Warm up IP (gradually increase send volume)
Issue 2: Cannot Receive Email
Symptoms: Outgoing works, but incoming fails
Check:
# Verify MX record
dig blueelysium.net MX
# Check if server is listening
netstat -tulpn | grep ":25"
# Test SMTP connection
telnet mail.blueelysium.net 25
# Check logs
docker logs mailserver | grep "error"
Common Causes:
- Port 25 blocked by ISP (can’t be fixed easily)
- Incorrect MX record
- Firewall blocking port 25
- Postfix not running
Issue 3: Authentication Failures
Symptoms: Client says “invalid password”
Debug:
# Test authentication directly
docker exec mailserver doveadm auth test frank@blueelysium.net
# Check password query
docker exec mailserver mysql -h db -u pfuser -p -e "SELECT username, password FROM mailbox WHERE username='frank@blueelysium.net'"
# Review Dovecot SQL config
docker exec mailserver cat /etc/dovecot/dovecot-sql.conf.ext
Issue 4: Fail2Ban Blocking Legitimate Users
Symptoms: Sudden connection timeouts after several login attempts
Fix:
# Check banned IPs
docker exec mailserver fail2ban-client status dovecot
# Unban IP
docker exec mailserver fail2ban-client set dovecot unbanip 10.0.6.91
# Add to whitelist (in fail2ban-jail.local)
ignoreip = 10.0.6.0/24
Issue 5: Disk Space Filling Up
Symptoms: Server running out of space
Check:
# Find large directories
du -sh /var/mail/* | sort -h
# Check mail logs
du -sh /var/log/mail/*
# ClamAV database
du -sh /var/lib/clamav
Solutions:
- Implement mailbox quotas in PostfixAdmin
- Rotate logs more aggressively
- Clean old mail from test accounts
- Set up automated cleanup scripts
The Webmail Question
I’m currently using desktop/mobile email clients, but considering adding webmail:
Options:
- Roundcube – Feature-rich, but heavy
- SOGo – Includes calendar/contacts
- SnappyMail – Modern, fast, lightweight
Consideration: Do I need webmail if desktop/mobile apps work great?
Conclusion
Building a self-hosted mail server was significantly more challenging than the web server, but also more rewarding. Email is mission-critical infrastructure that most people never think about – until they try to run their own!
The Stats
Time Invested:
- Initial research: 1 day
- Setup and configuration: 2 days
- Troubleshooting: 3 days (DNS, DKIM, Fail2Ban, authentication)
- Documentation: 1 day
- Total: ~1 week
Was It Worth It?
Sure, I now have:
✅ Complete email independence
✅ Deep understanding of email infrastructure
✅ Professional email system (frank@blueelysium.net)
✅ Privacy and data ownership
✅ Impressive DevOps portfolio project
✅ Skills that transfer to professional work
The Most Important Lesson
Email is hard for a reason. All the complexity – DKIM, SPF, DMARC, encryption, spam filtering – exists because email is a global, federated system fighting constant abuse. Self-hosting email isn’t just about running software; it’s about participating in a complex ecosystem built over decades.
But once everything clicks into place and that first email arrives with perfect DKIM/SPF validation, green security checkmarks everywhere, and lands directly in the inbox (not spam!) – that feeling makes every hour of debugging worth it.
Resources That Saved Me
Essential Documentation:
- docker-mailserver Documentation
- PostfixAdmin Documentation
- Postfix Documentation
- Dovecot Documentation
Testing Tools:
- mail-tester.com – Deliverability testing
- MXToolbox – DNS and email diagnostics
- dmarcian – DMARC testing
Community Resources:
- docker-mailserver GitHub Issues (so many similar problems!)
- Stack Overflow (specific DKIM/Dovecot questions)
- Reddit r/selfhosted (moral support!)
Final Thoughts
Self-hosting a mail server in 2025 is an anachronism. Gmail, Outlook.com, and other hosted services are free, reliable, and require zero maintenance. Most sysadmins will tell you it’s not worth the effort.
But for the learning experience alone, I’m glad I did it. I now understand email at a level most folks never will. When someone mentions DKIM or SPF, I don’t just know what they are – I’ve configured them, debugged them, and watched them work in production.
Plus, there’s something deeply satisfying about having frank@blueelysium.net as my email address, running on hardware I control, with no third-party reading my messages or selling my data.
Would I recommend this to others?
- If you want to learn: Absolutely. It’s educational.
- If you need reliable email: Probably not. Use Gmail.
- If you value privacy: Maybe. But be prepared for the commitment.
Email is the last piece of self-hosted infrastructure I needed. Combined with the WordPress site from Part 1, I now have a complete, production-ready platform for web and email, entirely under my control.
The journey from “How hard can email be?” to “Wow, email is complex!” taught me more than a dozen courses could have.
Thanks for reading! Questions? Want to share your own self-hosting journey? Reach out at frank@blueelysium.net (yes, it works! 😄)
Published: November 2025
Author: Frank @ BlueElysium
Series: Building a Self-Hosted Web & Mail Server
Part: 2 of 2
Read Part 1: Building the Web Server Foundation
Repository: Full configuration files and documentation available
Documentation: See docs/ directory for detailed guides
