fix(mail): route all outbound mail through local Stalwart SMTP; add DKIM and DNS records#17
Merged
Merged
Conversation
Bump platform_version and repo_version to 0.179.47. Update woodpecker live-apply receipt to reflect successful deployment of Woodpecker CI (ci.0mpc.com) with OpenBao secret injection, Gitea OAuth bootstrap, and seed repository activation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-05-26 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t delivery The gateway's send_via_local_smtp was authenticating with a global LOCAL_SMTP_USERNAME/PASSWORD env var, but Stalwart's must-match-sender enforces that the authenticated user matches the FROM address. With three profiles each sending from their own mailbox (alerts@, platform@, agents@) this caused all local SMTP attempts to fail and fall through silently to Brevo. Changes: - app.py: load mailbox_password per profile in load_notification_profiles() - app.py: use profile.mailbox_localpart + profile.mailbox_password as SMTP credentials in send_via_local_smtp; fall back to global env vars if absent - app.py: disable SSL cert verification for local Stalwart (self-signed cert) - defaults/main.yml: flip gateway defaults to attempt_local_first=true, force_brevo_fallback=false (local SMTP is now the primary path) - defaults/main.yml: add mail_platform_dkim_* variables for DKIM key management - stalwart-config.toml.j2: add conditional DKIM signing block - tasks/main.yml: add task to deploy DKIM private key before Stalwart config render All three profiles (operator-alerts, platform-transactional, agent-reports) now route through local Stalwart SMTP and return channel=local_smtp on success. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… DNS records - inventory/host_vars/proxmox-host.yml: replace Brevo-specific DNS records with local DKIM (mail._domainkey TXT), fix SPF to "v=spf1 mx ~all" (remove Brevo include), mark brevo1/brevo2 CNAME and brevo-code TXT as absent for cleanup - defaults/main.yml: add mail_platform_dkim_public_key variable (reads from .local/mail-platform/dkim-public-key.txt at runtime) Running make converge-mail-platform now sets all mail DNS records for deliverability without Brevo dependency. The bootstrap playbook (playbooks/mail-platform.yml) handles the full DNS + runtime converge. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DNS TXT records are limited to 255 chars per string. The DKIM public key (392 chars) plus the prefix (18 chars) totals 410 chars. Add mail_platform_dkim_dns_value in role defaults that splits the value into two quoted strings per DNS convention: "v=DKIM1; k=rsa; p=<chunk1>" "<chunk2>" Use mail_platform_dkim_dns_value in the inventory mail._domainkey record so make converge-mail-platform is idempotent on future runs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
send_via_local_smtpwas using a globalLOCAL_SMTP_USERNAME/PASSWORDenv var, but Stalwart'smust-match-sender=truerejects cross-account sends. Fixed to use each profile's ownmailbox_localpart+mailbox_password— all three profiles (operator-alerts,platform-transactional,agent-reports) now route via local SMTP and returnchannel=local_smtpATTEMPT_LOCAL_FIRST=true,FORCE_BREVO_FALLBACK=false— local Stalwart is now the primary path; Brevo is a dead fallbacksend_via_local_smtpstalwart-config.toml.j2; new task deploysdkim-private.pemfrom.local/; defaults exposemail_platform_dkim_public_keyandmail_platform_dkim_dns_value(auto-chunked for 255-char DNS limit)mail_platform_dns_recordsin inventory updated — SPF nowv=spf1 mx ~all,mail._domainkeyTXT added, Brevo CNAME/verification records markedstate: absentfor cleanupApplied to production
All DNS records are already live in Hetzner DNS zone for
0mpc.com(applied manually this session):mail A→65.108.75.123@ MX→10 mail.0mpc.com.@ TXT→"v=spf1 mx ~all"_dmarc TXT→"v=DMARC1; p=quarantine; rua=mailto:..."mail._domainkey TXT→ DKIM RSA-2048 public key (chunked)brevo1/brevo2._domainkeyCNAMEs deletedPending user action (not automated)
0mpc.comis registered at Hetzner but still uses parking NSes (ns1.expirationwarning.net). Update tons1.your-server.de,ns.second-ns.com,ns3.second-ns.devia Hetzner account → Domains65.108.75.123→mail.0mpc.comat https://robot.hetzner.comBootstrap playbook
make converge-mail-platform(which runsplaybooks/mail-platform.yml) now handles the complete bootstrap: DNS records, runtime converge, gateway rebuild, DKIM key deployment.Release checklist