CLAUDE.md described infrastructure that no longer exists. An agent following it would send aws ssm send-command to instance i-0e365c6bd31da4678 and wait for a reply that is never coming.
Verified against the live host:
| documented | actual |
|---|---|
| EC2, Amazon Linux 2023, us-east-1 | Hetzner 178.105.248.188, Ubuntu 24.04 |
| SSM agent running, deploy "without SSH" | no SSM agent installed; SSH is the only path |
/opt/mail/smtp.db | symlink to /var/lib/mail-storage/data/smtp.db |
/opt/mail/mail/{user}/new/ | symlink to /var/lib/mail-storage/data/mail/{user}/ |
| delivery via Amazon SES | SMTP_DELIVERY_METHOD=direct |
The systemd unit, /etc/mail/mail.env and the Let's Encrypt path were all correct and are unchanged.
Three things added because they cost real time
State lives under /var/lib. Everything in /opt/mail that holds data is a symlink, so backing up the path you first find backs up a symlink. There is now a table mapping each one.
A maildir is named after the username column, which is not always the email address. 12 of the current accounts are stored bare (cloud, zoltan, noreply) and the rest use the full address, so a bare-username account cannot authenticate with its address. In practice this is what an "I can't access my mail" report usually turns out to be.
mail.log is not rotated and was 345MB, so read a slice rather than the whole file.
Deployment section
Replaced with scp + systemctl restart, using the mail-server.bak-<epoch> convention the box already follows.
No setcap step. I wrote one in first and it was wrong: the binary carries no file capabilities (getcap returns empty) and port 25 works because the unit declares AmbientCapabilities=CAP_NET_BIND_SERVICE, which systemd grants at launch.
What is deliberately left
packages/cloud/cloud.config.ts still exists, so its section stays — but it is now marked as the AWS path the repo supports rather than a description of production, with a note to verify against the live box first.
Branched from main rather than pushed directly: the checkout this came from is 157 commits behind and carries an unrelated unpushed commit, so rebasing would have dragged that along.