Parent: uucp; transport: setup-status.
Deployed 2026-09-20 on pomelo, gravlax and kelp. Postfix local delivery plus authenticated UUCP transport. Addresses are john@pomelo, john@gravlax and john@kelp; bare john delivers locally. Mailboxes are /var/mail/john. Unix recipient accounts must already exist.
gravlax is the configurable hub. A spoke sends other hostnames to the hub, which routes to the destination. Short addresses are preserved (append_dot_mydomain=no, myorigin set to the node name). Explicit exact-name routes avoid DNS/MX lookups for these internal addresses. This does not require a .uucp pseudo-domain.
Only pomelo has scheduled polling: every 15 minutes, at :00/:15/:30/:45, using uucp-mail-poll.timer. It polls gravlax even with no outgoing mail, so waiting inbound mail is collected. Callable hosts call callable next hops on demand when mail is queued. Pomelo cannot be called; mail to/from it may wait until its next poll. No other polling timer is enabled. Failed on-demand calls leave queued UUCP work; without another call they need manual retry.
No connection to popit3, Envoy, Internet relays or IMAP was added. Existing pomelo Postfix maps and queued mail were preserved; its unrelated critchley.biz route to localhost:10025 remains as before. New Postfix installations on gravlax and kelp listen only on loopback and reject external destinations. Existing /var/mail/vhosts data was left alone.
printf 'Subject: hello\n\nHello\n' | /usr/sbin/sendmail john@pomelo
# Local submission: /usr/sbin/sendmail john
sudo systemctl start uucp-mail-poll.service
systemctl list-timers uucp-mail-poll.timer
sudo /usr/local/bin/uustat -a
postqueue -p
Sources on pomelo: /home/john/ansible/uucp_mail.yml, uucp_mail_topology.yml, roles/uucp_mail/, verify_uucp_mail.py. The combined uucp_mail_provision.yml imports the existing transport playbook then the mail playbook. Detailed file-based instructions: roles/uucp_mail/README.md. Changes remain uncommitted in the existing Ansible working tree.
cd /home/john/ansible
# Existing, verified UUCP transport:
ansible-playbook uucp_mail.yml
# Provision both layers, with existing stunnel PKI and topology:
ansible-playbook uucp_mail_provision.yml -e @secrets.yml
# Sends labelled internal test messages and checks actual mailbox Message-IDs:
python verify_uucp_mail.py
Change uucp_mail_hub in uucp_mail_topology.yml to move the hub. Ensure the corresponding UUCP peer links, credentials and firewall access first, then apply the playbook across all mail hosts. Drain old queued jobs before retiring the old hub: queued UUCP jobs retain their original next hop. Poll schedules are explicit entries in uucp_mail_poll; removing an entry disables that host's timer.
Postfix transports invoke /usr/local/bin/uux; queued transport uses -r, on-demand transport omits -r. UUCP command-path includes /usr/sbin for Postfix rmail. Both inbound uucico@.service and the poll service run uucico in foreground with -D -q and execute /usr/local/sbin/uuxqt via ExecStartPost. This avoids systemd killing the background mail executor when the connection ends.
Postfix service overrides permit writes to /usr/spool/uucp, the compiled lock directory, while retaining ProtectSystem. Without this exception Debian 13's Postfix sandbox prevents on-demand uucico from acquiring locks. Transport regexps match the full recipient address as well as a bare domain; matching only a bare hostname caused the first tests to bounce.
Before changes, pomelo's Postfix configuration was copied to /etc/postfix.before-uucp-mail. Mail queues were not deleted or flushed. To stop scheduled exchange, disable uucp-mail-poll.timer on pomelo; do not delete queued work. Initial diagnostic tests generated a few local bounces; successful labelled test messages were left in the mailboxes.
All nine local/inter-host paths passed by checking exact Message-ID headers in /var/mail/john, including the kelp→gravlax→pomelo path. Short From/To headers survived delivery. A repeat Ansible run reported changed=0 and failed=0 on all three hosts. Only pomelo's timer is enabled, every 15 minutes.