Internal mail deployed 2026-09-20, after John's approval. All nine delivery paths work. Current operational instructions: uucp/mail. Transport architecture: uucp/setup-status.
Secure mail connectivity between all nodes. That is the goal, stated 2026-09-18, and it is what SMTP does not really give you between hosts.
SMTP can be locked down — MTA-STS, DANE, mutual TLS between known relays — but the defaults are opportunistic. STARTTLS can be stripped, trust rests on public CAs and DNS, and a message can cross a relay nobody chose. The UUCP network is the opposite shape: a fixed set of named peers, one private CA, mutual certificate verification on every link, no discovery and no trust-on-first-use. Mail carried over it inherits all of that.
So the mail work is not 'get rmail running'. It is 'every hop between nodes is mutually authenticated, and mail rides that'. Judge design decisions against it.
John authorised internal mail and specifically requested only pomelo poll every 15 minutes (2026-09-20). That timer is now enabled. No other periodic polling is configured; callable hosts call on demand. External mail integration remains paused.
Manual hub poll: sudo systemctl start uucp-mail-poll.service. For a particular peer, run uucico -s <peer> -f as the uucp user. See uucp/mail for queues and delivery verification.
Internal portion implemented; external integrations below remain future work.
See popit3 for the inbound side.
1. Completed 2026-09-20: internal Postfix/rmail delivery and exact-name UUCP routes. External mail integration still awaits separate instructions.
2. Completed 2026-09-20: only pomelo polls gravlax every 15 minutes. Calls must occur even with no outgoing work to collect waiting inbound mail; the poll uses -f, not -C.
3. Ansible changes remain uncommitted in ~/ansible: transport roles/uucp/, uucp.yml and uucp_topology.yml; mail roles/uucp_mail/, uucp_mail.yml, uucp_mail_provision.yml, uucp_mail_topology.yml and verify_uucp_mail.py. secrets.yml remains gitignored.
Add it to uucp_topology.yml and to the peers list of each host it links to, give it a loopback port in the role defaults, generate uucp_pw_<a>_to_<b> for each direction, run the playbook, then run it again with --tags firewall if it is on EC2. It needs stunnel already configured with the shared CA; the role asserts that rather than inventing its own TLS.
The build task in roles/uucp/tasks/build.yml is Debian-only (ansible.builtin.apt); see PROGRAMMING_RULES/freebsd-build-from-source for the general build-from-source policy and why that task needs branching rather than translating.
FreeBSD ports carries net/freebsd-uucp (1.07.4_4, checked 2026-09-18) — upstream 1.07 with man-page path fixes, a CSTD=gnu99 pin, WITHOUT_PIE=true, and two off-by-default options. When actually building a FreeBSD node, John wants (2026-09-18):
Set via make config in the port directory, or non-interactively via OPTIONS_FILE / make variables — confirm the exact mechanism against the port's current Makefile at the time rather than assuming it still matches this note.
PIE (Position Independent Executable): compiled so the binary can load at a randomized base address each run (ASLR) rather than a fixed one, so a memory-corruption exploit can't hardcode addresses for a ROP chain. Small overhead cost. The port disables it (WITHOUT_PIE=true), most likely for build-system compatibility with 2003-era code rather than a deliberate tradeoff. Not touched by the instruction above; worth a look if the port build is revisited.
pomelo has no inbound path. Fine for polling, and fine for mail if pomelo initiates. If gravlax or kelp ever needs to push to pomelo promptly rather than waiting for a poll, that needs either a port forward for 10540 or a reverse tunnel.