Back to Blog
AWSDevOpsIaC

The Final Mile: Self-Hosted Email Server with Dovecot and Postfix

Connecting IMAP and SMTP to AWS SES for a full email stack on a cheap VPS

Summary

This final post connects all three layers — AWS SES, Rspamd, and a self-hosted mail server — into a working email stack you can use with any standard email client.

Self-Hosted Email Server with Dovecot and Postfix Architecture

The missing piece

After Post 1 and Post 2, incoming emails landed in S3 and were screened for spam. But raw email files in a cloud storage bucket aren't something Thunderbird or Apple Mail can open. Two more components were needed:

  • IMAP — so email clients can browse and read incoming mail
  • SMTP submission — so those same clients can send outgoing mail

Dovecot handles the IMAP side (the most widely deployed open-source IMAP server), and Postfix handles SMTP relay. Both run together in a single Docker container on the VPS, managed by supervisord.

How mail flows

Inbound: AWS SES receives the message and stores it in S3. A cron job running every five minutes downloads new email files from S3, maps each file to the correct recipient address based on the S3 folder it landed in, and delivers it into Dovecot's mailbox structure. The email client then fetches it from Dovecot over an encrypted IMAP connection.

Outbound: the email client submits a message to Postfix over SMTP (port 587, encrypted with STARTTLS). Postfix authenticates the user via Dovecot's user database, then relays the message to SES SMTP. SES handles DKIM signing and final delivery.

TLS everywhere

Email clients expect encrypted connections on both ports. A Let's Encrypt certificate for mail.joao-coelho.com covers both IMAP (port 993) and SMTP submission (port 587). Rather than using an HTTP challenge (which would require a web server on port 80), the certificate is issued via Cloudflare's DNS API — Certbot proves domain ownership by temporarily creating a DNS record.

The single hostname mail.joao-coelho.com serves all four domains. Email clients don't care which From: address you're using; they connect to whatever server you configure.

Security

Exposing ports 993 and 587 to the internet means brute-force login attempts are a given. Two layers of protection:

  • fail2ban runs on the VPS host and monitors container logs; five failed authentication attempts within five minutes triggers a one-hour IP ban
  • Application-level rate limits in both Dovecot and Postfix cap connection rates, message rates, and introduce a brief delay between failed login attempts

As with Rspamd in Post 2, dovecot, postfix and fail2ban have a lot of settings that I don't know about, nor do I want to. AI came to the rescue again by setting initial sensible values that can be tweaked as needed.
To give you an idea, here are the files used to configure them. Each file has several config entries that address very specific functionality:

  +-- dovecot
  |   +-- conf.d
  |   |   +-- 10-auth.conf
  |   |   +-- 10-mail.conf
  |   |   +-- 10-master.conf
  |   |   +-- 10-ssl.conf
  |   |   `-- auth-passwdfile.conf.ext
  |   +-- aliases.conf
  |   `-- dovecot.conf
  +-- fail2ban
  |   +-- filter.d
  |   |   +-- dovecot.conf
  |   |   `-- postfix-sasl.conf
  |   `-- jail.local
  +-- postfix
  |   +-- main.cf
  |   +-- master.cf
  |   +-- sasl_passwd
  |   `-- sender_login.regexp
  `-- supervisord.conf

Connecting an email client

Any standard client (Thunderbird, Apple Mail, Outlook) works with these settings:

Setting Value
IMAP server mail.joao-coelho.com, port 993, SSL/TLS
SMTP server mail.joao-coelho.com, port 587, STARTTLS
Username Full email address
Password Dovecot user password

After setup, outgoing mail appears immediately; incoming mail appears within five minutes (the S3 sync interval).

The complete stack

The full email infrastructure, from DNS to inbox:

Layer Technology Role
DNS Cloudflare MX, SPF, DKIM, DMARC, mail subdomain
Inbound gateway AWS SES MX reception, receipt rules → S3
Storage AWS S3 Versioned email storage per address
Spam filtering Rspamd + Redis (Docker) Bayesian analysis, RBL checks
Mail server Dovecot + Postfix (Docker) IMAP serving, SMTP relay
Security fail2ban + rate limits Brute-force protection
TLS Let's Encrypt (certbot) Encrypted connections
Infrastructure OpenTofu All AWS + Cloudflare resources as code

All of this runs on a single $15/month Hetzner VPS, using about 250MB of RAM for the entire email stack.