Audit: new-domain saga end to end with relay-only DKIM signing

closed
#3d450a9 opened by agent Sep 24

Goal

Trace the full workflow for adding a new domain and check that each step is wired correctly for the target architecture. The architecture went through several rewrites, so some parts may be connected wrongly, half-migrated, or dead.

Target architecture (assumptions to hold the code to)

  • Every configured domain sends outbound mail through a relay (SMTP2GO in production; Lettermint also supported).
  • Stalwart DKIM signing is off. Only the relay signs. sovrn publishes no DKIM key of its own.
  • The owner publishes:
    • MX @ → 10 <cell hostname> (mx[0-9]+.<region>.sovrn.at, plus any backup MX);
    • the relay’s required records. For SMTP2GO these are CNAME s<dkim_selector>._domainkey → dkim.smtp2go.net and CNAME em<rpath_selector> → return.smtp2go.net, with the selectors taken from the relay API.

Workflow to trace

Check each step for correctness, error handling, retries, idempotency and tests.

  1. Create a domain (UI createDomain → appview provisionDomainSaga / domain_create.go).
    • Does the saga register the sending domain with the relay API (relay.RegisterDomain) as a saga step, with compensation (RemoveDomain) on failure?
    • Or does registration only happen lazily in the verifier sweep (verifier.relayReadiness)? If lazy, the setup page shows “records not ready yet” at first. Decide which is intended.
  2. Stalwart domain and DKIM.
    • provision.go calls dkim.Generate unconditionally and stores DkimSelector/DkimTxt on the domain row.
    • With relay-only signing, this key should not be created. Stalwart should also not be signing with it. Check the Stalwart signing configuration and relay.ApplyOutbound / MtaRoute settings.
    • Check that removal (dkim.DeleteForDomain) and orphan sweeps still make sense once the key is removed.
  3. Getting the records.
    • Check that RegisterDomain and CheckDomain return the correct names and values for each provider:
      • SMTP2GO mapSMTP2GODomain: the fixtures assume dkim_selector already includes the s prefix and rpath_selector the em prefix. Verify this against the real API. Also note the empty-selector fallbacks and that tracker CNAMEs are named at the apex.
      • Lettermint toDNSRecord: FQDN vs hostname.
    • Check where the records are persisted. Today the verifier stores them in relay_records after CheckDomain, and the UI fetches them once on demand.
  4. Setup page (/domains/{id}, ui.buildDNS).
    • With a relay it shows MX plus the relay’s required records by relative name.
    • Confirm the non-relay branch (SPF TXT + local DKIM TXT) can be deleted, or is still needed for dev.
    • Check the XRPC surface as well: domain_get_dns_state.go and the api/sovrn lexicon domaindetail/domaindefs still expose DkimSelector/DkimTxt, and appview/dns.go may build the old record set.
  5. Verification and activation (verifier sweep, dnsprober, recheck button).
    • Relay branch: t.SPF and t.DkimTxt are now cleared. Activation requires the MX probe, plus CheckDomain returning verified, plus the relay records via RelayRecords.
    • Check whether the relay records are checked by direct DNS lookup (the CNAME targets), by the provider’s verified flags, or both. Decide which is authoritative and what happens when they disagree.
    • Check the 15-minute CheckDomain cache vs the “Check again” button: does a recheck bypass the cache?
    • Check the transition into active and what it triggers (Stalwart domain enable, the relay route).
  6. Ongoing.
    • Re-verification or drift detection after activation (the owner changes DNS provider, or the relay revokes the domain).
    • Domain removal: RemoveDomain at the relay, relay_records cleanup (DeleteDomain should delete them), and route withdrawal.

Also check

  • router.go wiring: that relayInst reaches every consumer (saga, verifier, UI, webhooks). Check the behaviour when the relay is nil (dev), and whether production can start without a relay.
  • Config and ansible: the relay credentials, and sovrn_mail_hostname being the cell hostname.
  • Tests that still assert the old SPF/DKIM TXT record set as the production path.

Deliverable

A written trace of each step: the file and function, what it does today, and whether it matches the target. Then a list of concrete defects and gaps, each filed as a sub-issue under this one in priority order.

Context

The domain page polish work (relative DNS names, relay CNAMEs on the setup page, relay records persisted by the verifier, and SPF/DKIM TXT dropped from the relay readiness gate) is in the current working copy, not yet committed.

1 Comment

agent 38dd4c5 Oct 8

Superseded by 62aba1d (2026-10-08), which restates this audit from its current status after the live run on mx99: what’s verified (domain to active, SMTP2GO selectors, dkim/spf/dmarc pass), the defect found (Stalwart holds an unpublished per-domain DKIM key it marks as used for signing; dkim.Generate still runs in the saga), and the audit questions still open. Closing per the user: consolidate from current status rather than nest more children.