Stalwart Email (RETIRED — replaced by postmark-email)
Vision
Self-hosted SMTP infrastructure for the Pal-E platform. Stalwart Mail Server on the Hetzner edge VPS enables custom-domain transactional email (password resets, notifications) without third-party dependencies. Each service realm gets its own sender identity (noreply@landscaping-assistant.app, noreply@westsidekingsandqueens.com). Infrastructure lives in pal-e-platform (Salt state), per-domain configuration in pal-e-services (DNS + Keycloak SMTP). Zero Gmail dependency for auth flows.
User Stories
| Key | Role | Story | Success Metric |
|---|---|---|---|
| password-reset | End User | As a user who forgot my password, I want to receive a reset email from my app's domain so I can regain access | Keycloak "Forgot Password" sends email from noreply@{domain}, lands in inbox (not spam), link works |
| custom-domain | Platform Admin | As platform admin, I want each app to send from its own domain so emails look professional and build domain reputation | Each realm configured with distinct sender. SPF/DKIM/DMARC all pass. mail-tester.com score 9+/10 |
| self-hosted | Platform Admin | As platform admin, I want zero third-party email dependencies so I control deliverability and avoid Gmail app password rotation | Stalwart handles all outbound SMTP. No Gmail OAuth tokens needed for auth flows. |
Architecture
Key Decisions
- Stalwart on edge VPS (not k3s) — mail server must live on the IP with PTR/rDNS for deliverability. Home ISPs block port 25 and don't support rDNS.
- SaltStack managed — single binary + systemd, same as Caddy. Fits existing edge VPS management pattern.
- Outbound-only — no inbound MX. Only port 587 (submission from Keycloak via Tailscale) and port 25 outbound (delivery to remote MX).
- SMTP AUTH per realm — each Keycloak realm authenticates as its own noreply@ account. Auditable, defense-in-depth.
- Caddy for web admin only — mail.landscaping-assistant.app reverse proxies to Stalwart's HTTP admin UI.
Board
board-stalwart-emailStatus
Not started. Research complete. Tickets scoped. First blocker: Hetzner port 25 unblock request.
Milestones
None yet.
Repos
| Repo | Platform | Role |
|---|---|---|
| pal-e-platform | Forgejo | Salt state for Stalwart install, Hetzner firewall rules, PTR record |
| pal-e-services | Forgejo | DNS records per domain (SPF/DKIM/DMARC), Keycloak SMTP config per realm |
Related
project-pal-e-mail— application-level email sending service (Gmail-based). Future: could migrate to use Stalwart as backend.