# Mail A single mail server, `mx1`, runs the [Simple NixOS Mailserver](https://nixos-mailserver.readthedocs.io/) 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](./dns.md#acme-dns-01). ## Add a mailbox 1. Append the address to `accounts` in `modules/mail.nix`: ```nix accounts = [ "postmaster@cnx.email" "alice@cnx.email" # new ]; ``` 2. Mint its generated password (user-run, YubiKey touch): ``` clan vars generate mx1 ``` 3. Redeploy: `clan machines update mx1`. 4. 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: 1. **Serve the zone** (if not already): add it in `modules/dns/domains.nix` and drop a `.zone` file — see [DNS](./dns.md). 2. **Declare the domain** in `modules/mail.nix`: ```nix mailserver.domains = [ "cnx.email" "newdomain.tld" ]; ``` Add at least a `postmaster@newdomain.tld` account (RFC 5321 requires it) the same way as above. 3. **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` (selector `mail`). After deploy, read the public key off `mx1` and paste it as `mail._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 existing `dmarc@cnx.email` inbox. 4. **DANE / MTA-STS are tied to `mx1.cnx.email`**, the MX hostname — they do **not** need duplicating per sending-domain. A new domain that uses `mx1.cnx.email` as its MX inherits the existing TLSA and MTA-STS policy. Only add new TLSA/MTA-STS records if you introduce a new MX hostname. 5. `clan vars generate mx1` (if you added accounts), then `clan 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 the `mail._domainkey TXT`. - **DANE TLSA** (`_25._tcp.mx1 TLSA 3 1 1 …`) is the SHA-256 of `mx1`'s cert **public key**. It stays valid across renewals because lego runs with `--reuse-key`; only recompute it if the key changes. The `openssl` one-liner is in the zone file. - **MTA-STS**: bump the `_mta-sts TXT` id **every time** the policy in `modules/mail.nix` changes, 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](./monitoring.md).