Stalwart Email (RETIRED — replaced by postmark-email)

project-stalwart-email Project Page

active project-page

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-email

Status

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
  • project-pal-e-mail — application-level email sending service (Gmail-based). Future: could migrate to use Stalwart as backend.