Both existed only on the live server / untracked locally until now. Includes the open/closed status fix: the Filiale status badge was hardcoded as "Geöffnet · schließt um 20:00" regardless of actual time (caught showing "open" at 3am) -- now computed against the shop's own Europe/Berlin clock and real weekly hours. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4.4 KiB
Playbook — Mailcow Administration
Provisioning and maintaining transactional email for smb-online clients. Mailcow lives at
https://mail.mivanchenko.de/ (VPS, not the homelab box — separate host, already the MX for
mivanchenko.de, already has a correct PTR record). DNS for mivanchenko.de and its subdomains
is hosted at 1&1/IONOS (ns1107.ui-dns.com).
This is the reference for anyone (human or agent) provisioning a new client's mail, or doing general mailcow upkeep.
What "client mail" means here
Each client gets a per-client sending identity: noreply@<slug>.mivanchenko.de. The smb-crm
backend (backoffice/app/booking_mail.py:_sender_for) auto-derives the From address from the
client's domain field — it always sends as noreply@<domain>, never anything else, and this is
not configurable per-send. Plan mailbox naming around that fact rather than fighting it.
Agent brief
You manage transactional email for the
smb-onlineclients via mailcow (https://mail.mivanchenko.de/, admin UI) and 1&1/IONOS DNS formivanchenko.de.Your job, when asked to set up mail for a new client
<slug>.mivanchenko.de:
- In mailcow: add
<slug>.mivanchenko.deas a domain, generate a 2048-bit DKIM key for it, create one real mailbox (commonlymailing@<slug>.mivanchenko.de— a real inbox someone monitors), and addnoreply@<slug>.mivanchenko.deas an alias of that mailbox with "Als dieser Alias senden erlauben" (send-as) enabled — this is required becausebooking_mail.pyalways sends Fromnoreply@<domain>, never from the real mailbox address.- Tell the operator the exact DNS records to add at 1&1/IONOS: MX (
<slug>→mail.mivanchenko.de, priority 10), SPF TXT (<slug>→v=spf1 mx -all), DKIM TXT (host + value from mailcow's key page), and DMARC TXT (_dmarc.<slug>→v=DMARC1; p=none; rua=mailto:mailing@<slug>.mivanchenko.de). You do not have DNS write access yourself unless explicitly given IONOS credentials — default to instructing the operator rather than assuming access.- Once the mailbox password is available, set on the homelab server (
/home/mivanchenko/smb-crm/.env, thendocker compose up -d --no-deps --force-recreate smb-crm):SMTP_HOST=mail.mivanchenko.de,SMTP_PORT=587,SMTP_USERNAME=mailing@<slug>.mivanchenko.de,SMTP_PASSWORD=<password>,MAIL_FALLBACK_FROM=noreply@<slug>.mivanchenko.de. Append-only — never overwrite existing.envlines, and nevercat/print the file's existing contents (it holds live secrets for other services too).- Verify: trigger one real booking through
/book/<slug>and confirm the confirmation email actually arrives (check spam folder too, first time for a new domain).Guardrails: never delete a mailcow domain/mailbox/alias without explicit confirmation from the operator — that's a hard stop. Never disable "Als dieser Alias senden erlauben" on an alias that's actively used as a From address, since that silently breaks confirmation email delivery (it fails closed — mailer.py's
_send_nowlogs and swallows the error, so this fails silently unless someone checksdocker logs smb-crm). Treat mailbox passwords the same as any other secret: never read them from.envon the server, only accept them freshly from the operator or generate+set them without echoing back.
New client mail checklist (condensed)
- Mailcow → Mail Setup → Domains → add
<slug>.mivanchenko.de. - Mailcow → Mail Setup → Mailboxes → add
mailing@<slug>.mivanchenko.de+ strong password. - Mailcow → ARC/DKIM keys → generate for
<slug>.mivanchenko.de→ copy the TXT record. - Mailcow → domain → Add alias →
noreply@<slug>.mivanchenko.de→ targetmailing@<slug>.mivanchenko.de→ keep "Als dieser Alias senden erlauben" checked. - 1&1 DNS: MX, SPF, DKIM, DMARC records per the agent brief above.
smb-crm/.env:SMTP_*+MAIL_FALLBACK_FROM, then recreate the container.- Test-book through the client's public booking page, confirm the email lands.
Known constraint
Sender identity is fixed to noreply@<client's domain field> by the current code — there is no
per-client override to send from a different local part (e.g. mailing@ or termine@). If a
client specifically wants a different visible sending address, that needs a small
booking_mail.py change (a sender_local_part column or similar), not just mailbox setup.