Email system on Postfix - encrypted mail for a management infrastructure
category: solutionz · date: 2019-01-01 · updated: 2026-10-02 · author: LALA
Between 2017 and 2019 I designed, built and ran the email system of the management infrastructure of a network service provider. Its users were mostly not people. Servers, applications and appliances sent alerts and notifications through it, and what they sent described the inside of the infrastructure, so a message was not allowed to leave unless the sender was known, was allowed to write to that destination, and the content was encrypted for the person who would read it.
This Solution is the whole of that system. In each of two sites: a pair of Postfix servers behind one Keepalived address, Dovecot as their authentication back end, senders authorized by Active Directory groups over LDAPS, a content filter written in Bash that encrypts every message with S/MIME or PGP/MIME, two relay servers that deliver to the internet through NAT, and a third internal server with the mailboxes and Roundcube webmail. It started in 2017 as one internal server and one relay; the highly available design is from 2019.
noteBuilt on RHEL 7 with Postfix 2.10 and Dovecot 2.2. Every configuration file and script is shown as it ran and then checked against the releases current on 2026-10-02: Postfix 3.11, Dovecot 2.4, Keepalived 2.4, Roundcube 1.7, RHEL 10. The system is anonymized: sites, names, domains, addresses, people and organisations are replaced, and every secret is a placeholder. The configuration contains mistakes; they are reported where they were found, not corrected.
The system in one picture
mermaid
flowchart TB
clients["Mail clients: servers, applications, appliances"]
users["Administrators and operators"]
subgraph site["Site 2, DC2"]
vip["VIP DC2-S-XCMSX001, SMTP 25 with STARTTLS and SMTPS 465"]
subgraph pair["Internal pair, Keepalived"]
x1["DC2-A-VCMSX001: Postfix, Dovecot SASL, encryption filter"]
x2["DC2-B-VCMSX001: Postfix, Dovecot SASL, encryption filter"]
x1 <-. "keys and certificates, rsync" .-> x2
end
ad["Active Directory, LDAPS"]
x3["DC2-A-VCMSX002: mailboxes, Dovecot, Roundcube"]
r1["DC2-A-VCMSR001: relay, primary"]
r2["DC2-B-VCMSR001: relay, fallback"]
end
inet["Mail servers on the internet"]
clients --> vip
vip --> x1
vip -.-> x2
x1 --> ad
x1 -- "own domain" --> x3
x1 -- "other domains" --> r1
x1 -.-> r2
r1 -- "NAT" --> inet
r2 -- "NAT" --> inet
users -- "HTTPS" --> x3
The fictional environment
Every Article and every Config document uses the same names and addresses.
| Thing | Site 1, production | Site 2, pre-production |
|---|
| Internal pair | DC1-A-VCMSX001, DC1-B-VCMSX001 | DC2-A-VCMSX001, DC2-B-VCMSX001 |
| Virtual address of the pair | DC1-S-XCMSX001, 10.11.19.33 | DC2-S-XCMSX001, 10.12.19.33 |
| Mailbox and webmail server | DC1-A-VCMSX002 | DC2-A-VCMSX002 |
| Relay servers | DC1-A-VCMSR001, DC1-B-VCMSR001 | DC2-A-VCMSR001, DC2-B-VCMSR001 |
| Mail domain, also the directory domain | ad.example.net | ad-dc2.example.net |
| Internal mail network, VLAN 1168 | 10.11.19.32/28 | 10.12.19.32/28 |
| Relay network, VLAN 1172 | 10.11.19.64/28 | 10.12.19.64/28 |
| Public addresses of the relays | 198.51.100.44, 198.51.100.48 | 203.0.113.44, 203.0.113.48 |
| Host names | adm.example.net | adm.example.net |
Site 2 is the worked example throughout, because the archived configuration is that of its five servers. Site 1 is described from the design. People in mail addresses are user01 and up, partner organisations in group names are PARTNER1 and PARTNER2, and the marks CFG-ON and CFG-OFF in the comments of a file are mine: this setting was turned on, or off, by me.
Articles
Read in this order; it goes from why, through how it was built, to how it was run.
| # | Article | What it covers |
|---|
| 1 | Requirements and concept | What the system had to do, the first concept of 2017, and what the highly available one changed |
| 2 | Logical design | Naming, components and their jobs, service accounts, the four kinds of clients, the path of a message |
| 3 | Network, DNS and firewall | Two VLANs, NAT, the public records, the flows, and design tables that disagree with the servers |
| 4 | Virtual machines and OS build | Placement, sizing, file systems, packages, local accounts, three SELinux modules |
| 5 | Internal servers: Postfix | Listeners, routing, the restriction lists step by step, and a check that port 465 skips |
| 6 | Active Directory integration | Accounts, groups and roles, and the five LDAP maps filter by filter |
| 7 | Dovecot authentication | Dovecot as the SASL back end of Postfix, two LDAP password databases, what is stock and what is local |
| 8 | TLS and certificates | The internal CA, a certificate per server, which hops are encrypted and which are not |
| 9 | Automatic email encryption | The Bash content filter: S/MIME, PGP/MIME, exception lists, and where the script differs from its design |
| 10 | High availability and key synchronization | The Keepalived address, what fails over and what does not, two rsync services that can undo each other |
| 11 | Relay servers | Two interfaces, static routes, NAT, header cleaning, and names that do not match the DNS |
| 12 | Mailbox server | Virtual mailboxes, Dovecot delivery into Maildir, IMAP for the webmail |
| 13 | Webmail | Roundcube on a hardened Apache with PHP and MariaDB |
| 14 | Accounts, clients and operations | Giving an account mail, client settings, monitoring, logging, known issues, what I would do differently |
Configuration
Each document holds one file as it ran, with comments, the differences between the hosts that carry it, and a check against the current release. Files that are mostly the distribution's own comments are shown with every active line and without those comments.
Postfix, internal pair
Postfix, LDAP maps against Active Directory
Dovecot
Encryption filter
High availability and synchronization
Relay servers
Mailbox server
Webmail
Operating system
What the write-up found
Reading the archived configuration against the design, seven years later, turned up things the design does not say. Each is told in its Article.
| Finding | Where |
|---|
| The listener on port 465 replaces the recipient restrictions, so the group check for sending to external domains does not apply there | 5 |
| The sender-login check stands behind a rule that has already accepted the sender | 5 |
| Clients with neither TLS nor authentication, which the requirements promised to serve, cannot send through the pair | 14 |
| The relays offer no STARTTLS, so the hop from the pair to the relay is not encrypted | 8 |
The filter encrypts S/MIME with the default cipher of openssl smime, which was triple DES at the time | 9 |
Two synchronization services with --delete push in both directions | 10 |
| The relays introduce themselves with names that the public DNS of the design does not hold | 11 |
| IMAP listens on every IPv4 address of the mailbox server, without TLS, where the design says localhost | 12 |