[follow-up] Stalwart local delivery + relay split verification on smoke server with full DNS
closedGoal
Verify on a fresh smoke-test server (new hosted domain with full DNS credentials) that Stalwart delivers mail locally for same-domain user-to-user mail while routing all other recipients through the configured outbound SMTP relay (sovrn-relay). Clear the stale NOTE in internal/relay/stalwart.go:35-36 (“NOT yet confirmed against a live Stalwart”).
Blocked until: new smoke test server is stood up + a hosted domain with full DNS credentials is available.
Background / investigation result
Investigation (2026-09-16) concluded YES — this split is the documented Stalwart pattern and is already coded:
internal/relay/stalwart.go:51-87(ApplyOutbound): createsMtaRoute sovrn-relay(@type: Relay, smtp,authSecret: {"@type":"Value"}), then setsMtaOutboundStrategy.singleton.route = IF is_local_domain(rcpt_domain) THEN 'local' ELSE 'sovrn-relay'(index-keyedmatch: {"0":...}, single-quoted literals), disables cell DKIM (SenderAuth.dkimSignDomain: {"else":"false"}, sign-at-relay-only), thenAction ReloadSettings.- Upstream docs (
stalw.art/docs/mta/outbound/routing,/strategy; defaultroute: {"else":"'mx'","match":[{"if":"is_local_domain(rcpt_domain)","then":"'local'"}]}):routeis evaluated per-recipient;Localwrites to mailbox store (JMAP/IMAP/POP3, no params);Relayforwards to smarthost; replacing defaultelse: 'mx'with'sovrn-relay'is the supported smarthost-with-local-delivery shape. Migration guide shows the same, plusis_local_address(rcpt)-first variant for split-delivery. - Other strategies untouched is correct: default
schedulealready returns'local'for local rcpts. - Wire-shape tests:
internal/relay/stalwart_test.go(TestApplyOutboundWireShape,RouteExistsTolerated,Abort,EmptySecret,TransportError).
Risks to clear live (from support threads on silent fallback to direct-MX):
1. local route must exist (fresh bootstrap ships local+mx; we only add sovrn-relay, never delete — confirm via stalwart-cli get MtaRoute).
2. is_local_domain arity: code uses 1-arg is_local_domain(rcpt_domain), matching current default; 2-arg ('', rcpt_domain) examples in docs are stale — do not “fix” without live evidence.
3. Name-miss fallback: if route returns a name with no matching MtaRoute.name, Stalwart logs smtp.id-not-found / Gateway not found and falls back to direct-MX (looks like “relay ignored”). Requires exact sovrn-relay match + reload. Community ed. has no Trace objects — diagnose via raised log level + journal.
4. implicitTls per provider: 587/STARTTLS → false; 465 → true (router.go:applyRelayRoute overlay — verify Lettermint vs SMTP2GO defaults).
Scope / verification plan
On the new smoke server, with relay configured (Lettermint or SMTP2GO) and a fully-DNS’d hosted domain (MX + SPF + relay CNAMEs verified):
- Config dump:
stalwart-cli get MtaOutboundStrategy,query MtaRoute; assertroute.match[0] == is_local_domain(rcpt_domain) → 'local',else == 'sovrn-relay'; relay object isRelaywith correct host/port/auth; no duplicate routes after restart (re-apply toleratesprimaryKeyViolation). - Local test:
alice@<hosted> → bob@<hosted>via submission. Assert arrival via IMAP/JMAP, no connection to relay host (relay stub counter / relay dashboard / packet capture), queue showslocalschedule. - External test:
alice@<hosted> → external. Assert relay receives AUTH+message with single relay DKIM-Signature, SPF/DMARC pass, correct return-path; Stalwart does not attempt direct MX. - Mixed recipients: one message to local + external → split: local copy + one relayed copy.
- Unknown local recipient:
alice → nosuch@<hosted>— currently routeslocalthen bounces locally. Confirm this is desired (vs split-delivery relay viais_local_address). - Log check: no
Gateway not found/id-not-foundaround deliveries; on any direct-MX sighting, treat as name-resolution failure, not relay failure. - Docs cleanup: on green, delete stale NOTE in
internal/relay/stalwart.go:35-36, record verified wire shape + 1-argis_local_domainindocs/05-stalwart-integration.md.
Acceptance
- [ ] Smoke server + full-DNS hosted domain provisioned; relay configured via
sovrndconfig. - [ ] Config dump matches expected strategy/route objects.
- [ ] Local user-to-user mail delivered locally with zero relay hits.
- [ ] External mail goes via
sovrn-relayonly (no direct-MX fallback). - [ ] Mixed-recipient split verified; unknown-local-recipient behavior decided.
- [ ] Stale live-verification NOTE removed; docs updated.
Non-goals
No routing-expression redesign (unless live results force is_local_address split-delivery); no DKIM re-enablement on cell (sign-at-relay-only stands); no warmup/deliverability study.
Open questions (from investigation)
- Unknown recipient in hosted domain: local bounce (current) or upstream relay?
- Mixed-recipient policy: per-recipient split (Stalwart default) vs force-whole-message-via-relay if any external rcpt (DKIM/SPF alignment)?
- DKIM: relay signs for customer domains, or per-domain keys +
SenderAuthfollow-up needed?
1 Comment
Superseded by e9d14c1 (2026-10-08): restated from current status on mx99 (routes and strategy verified live, external path via SMTP2GO verified; local, mixed and unknown-recipient cases and the docs cleanup remain).