4.0 KiB
A single mail server, mx1, runs the Simple NixOS Mailserver
stack (Postfix + Dovecot + Rspamd + OpenDKIM) for the cnx.email domain. All of
it is declared in modules/mail.nix, imported by machines/mx1/configuration.nix.
Mailboxes are virtual (not system users): each address is a login account
whose password is a four-word passphrase auto-generated by a clan vars generator
(e.g. otter-lantern-cobalt-driftwood-42). The generator stores both the
passphrase (handed to the user) and its sha-512 hash (consumed by SNM); plaintext
never lands in the repo.
The mail cert is obtained by mx1 itself via ACME DNS-01 against ns1 (key
acme_mx1), and carries mx1.cnx.email plus the mta-sts.cnx.email and
mail.cnx.email SANs. See DNS → ACME DNS-01.
Add a mailbox
- Append the address to
accountsinmodules/mail.nix:accounts = [ "postmaster@cnx.email" "alice@cnx.email" # new ]; - Mint its generated password (user-run, YubiKey touch):
clan vars generate mx1 - Redeploy:
clan machines update mx1. - Hand the passphrase to the user. The generator name replaces
@→-at-and.→-:clan vars get mx1 mail-passwd-alice-at-cnx-email/passphrase
The DMARC report inbox (dmarc@cnx.email) is special: its password comes from
the shared mail-dmarc-cred generator (not the per-machine set) so parsedmarc
on control can read the same passphrase over the mesh.
Add a domain
Adding a second mail domain is more than a config line — it needs its own DNS records and DKIM key. Steps:
- Serve the zone (if not already): add it in
modules/dns/domains.nixand drop a<domain>.zonefile — see DNS. - Declare the domain in
modules/mail.nix:Add at least amailserver.domains = [ "cnx.email" "newdomain.tld" ];postmaster@newdomain.tldaccount (RFC 5321 requires it) the same way as above. - Publish the mail DNS records in the new zone, mirroring
cnx.email's; ---- Mail ----block:MX 10 mx1.cnx.email.— point at the existing MX; do not stand up a new hostname unless you really want a separate server.- SPF:
@ TXT "v=spf1 mx -all". - DKIM: SNM generates a key per domain on first start under
mailserver.dkimKeyDirectory(selectormail). After deploy, read the public key offmx1and paste it asmail._domainkey TXT. The private keys are declared as clan state and backed up. - DMARC:
_dmarc TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@cnx.email; ..."— reports can keep flowing to the existingdmarc@cnx.emailinbox.
- DANE / MTA-STS are tied to
mx1.cnx.email, the MX hostname — they do not need duplicating per sending-domain. A new domain that usesmx1.cnx.emailas its MX inherits the existing TLSA and MTA-STS policy. Only add new TLSA/MTA-STS records if you introduce a new MX hostname. clan vars generate mx1(if you added accounts), thenclan machines update mx1.
DKIM, DANE, MTA-STS reminders
These three are easy to get subtly wrong; the live records for cnx.email and
the exact commands to regenerate them are in modules/dns/zones/cnx.email.zone:
- DKIM key rotation = regenerate under
dkimKeyDirectory, then republish themail._domainkey TXT. - DANE TLSA (
_25._tcp.mx1 TLSA 3 1 1 …) is the SHA-256 ofmx1's cert public key. It stays valid across renewals because lego runs with--reuse-key; only recompute it if the key changes. Theopensslone-liner is in the zone file. - MTA-STS: bump the
_mta-sts TXTid every time the policy inmodules/mail.nixchanges, or senders keep the cached policy.
DMARC reporting
mx1 delivers aggregate/forensic DMARC reports to dmarc@cnx.email; parsedmarc
on control polls that mailbox over the mesh and feeds the results into Grafana.
See Monitoring.