order confirmation
All checks were successful
Deploy / build-deploy (push) Successful in 6m14s

This commit is contained in:
Jorijn van der Graaf 2026-08-09 00:14:09 +02:00
commit 2fa6e70af1
10 changed files with 619 additions and 20 deletions

View file

@ -315,6 +315,72 @@ unattended; the keyring lives in the 0700 StateDirectory. With a key configured,
a signing failure is a 500 — an unsigned invoice is never served by accident.
Without one (dev), invoices carry a visible UNSIGNED marker.
## Order email (confirmation + invoice)
A paid order gets one confirmation email with the clearsigned invoice
attached — plain text plus a markdown attachment, no HTML part, no remote
resources, nothing the privacy notice would have to explain. The mailer
watches the ledger, so every path to paid (reconciler, arrival poll, a manual
`--mark-paid` even on a later restart) results in exactly one email: the
`notified` event, appended only after the mail command accepts the message,
is what stops a resend.
Delivery shells out to a **sendmail-compatible command** rather than speaking
SMTP itself, for the same reason invoices shell out to gpg: TLS, AUTH and
deliverability are exactly what msmtp already does well, and the volume is a
handful of messages per week. Deliverability stays the mailbox provider's
problem (SPF/DKIM are theirs), and no third party beyond the provider that
already handles `info@catcrafts.net` ever sees order data — which is what the
privacy page implies.
```sh
apt install msmtp
cat > /etc/msmtprc <<'CONF'
defaults
auth on
tls on
tls_starttls on
account catcrafts
host smtp.your-mail-provider.example
port 587
from info@catcrafts.net
user info@catcrafts.net
passwordeval cat /etc/catcrafts/smtp-password
account default : catcrafts
CONF
chmod 0644 /etc/msmtprc
# msmtp runs as the service user, so the password file must be readable by
# it — unlike payments.env, which only root (systemd) reads.
install -o catcrafts -g catcrafts -m 0600 /dev/null /etc/catcrafts/smtp-password
# ...then put the SMTP password in that file.
```
Then in `/etc/catcrafts/payments.env`:
```
MAIL_COMMAND=msmtp -t
MAIL_FROM=Catcrafts <info@catcrafts.net>
```
and `systemctl restart catcrafts-server` — the journal should say
`mail: order confirmations via 'msmtp -t'`. Unset, no email is sent and the
order page plus the invoice download remain the buyer's receipt: degraded,
not down, like every optional integration here.
Worth knowing:
* A failed handoff retries with exponential backoff (1 min doubling to a cap
of ~an hour), forever — a broken relay delays the email, it never eats it.
Watch `journalctl -u catcrafts-server | grep 'mail:'` after changing config.
* With a signing key configured, a gpg failure means the email WAITS — an
unsigned invoice never leaves by accident, same rule as the download.
* Send a real test: `--rail=fake` locally with `MAIL_COMMAND` pointing at
msmtp and your own address in the order form, or just run `tools/e2e.sh`,
which captures the messages with a fake sendmail and verifies the attached
signature.
## Reading the orders ledger
```sh
@ -435,10 +501,11 @@ split working as designed.
tools/fetch-posts.sh # pull posts from the allowed communities
tools/fetch-media.sh [dir] # mirror their media locally (run after the above)
tools/fetch-rates.sh # ECB reference rates for the indicative prices
tools/e2e.sh # 143 HTTP checks against a real server; the CI gate
tools/e2e.sh # ~200 HTTP checks against a real server (135 while
# coming-soon; the rest re-arm at launch); the CI gate
crafter-build --local -r # the wasm app alone, no backend, on :8080
<server>/catcrafts-server --selftest # ~140 in-process assertions
<server>/catcrafts-server --selftest # ~260 in-process assertions
<server>/catcrafts-server --routes # status + title for every route
<server>/catcrafts-server --render /projects # dump one page's HTML
<server>/catcrafts-server --orders FILE # the orders ledger + manual transitions

View file

@ -79,6 +79,9 @@ ReadOnlyPaths=/srv/catcrafts-app /srv/catcrafts.net
# MOLLIE_API_KEY=live_... (or test_... while verifying) — the rail
# SENDCLOUD_PUBLIC_KEY / SENDCLOUD_SECRET_KEY / SENDCLOUD_METHOD — optional,
# live shipping rates; zone table without them
# INVOICE_GPG_KEY=... invoice signing (see deploy/README.md)
# MAIL_COMMAND=msmtp -t order confirmation email (see deploy/README.md,
# MAIL_FROM=... "Order email"); unset = no email is sent
# BUNQ_API_KEY=... legacy: only used when no Mollie key is set
# The '-' prefix makes the file optional: without it the server starts with
# payments off and the shop renders but refuses checkout — degraded, not down.