Skip to content

Email — Configuration Lifecycle

EmailConfiguration carries two orthogonal state regions, managed by a real state engine (EmailConfigurationLifecycle) with entry actions that perform external work against Postmark and Route 53. A configuration is sendable only when both regions agree: canSend() = operationalStatus ∈ {OK, DEGRADED} ∧ administrativeStatus = UNLOCKED.

States: PENDING → PROVISIONING → AWAITING_VERIFICATION → OK, with DEGRADED, MISSING, UNVERIFIED, and FAILURE as off-path states.

  • PENDING — created; no external resources yet.
  • PROVISIONING — entry action runs the 4-step sequence (createServer → createSignature → installWebhook → writeRecords; see Provisioning & sending). Success → AWAITING_VERIFICATION; failure → MISSING (with the compensation cascade already unwound).
  • AWAITING_VERIFICATION — a bounded polling coroutine checks Postmark verification. The row advances to OK when the gate DKIMVerified ∧ ReturnPathDomainVerified is satisfied; if the give-up threshold (default 24h) is exceeded it moves to UNVERIFIED. (SPF verification is deprecated by Postmark and not used; Confirmed is not required for an automated noreply@ address.)
  • OK — sendable (subject to the administrative region).
  • DEGRADED — sendable but flagged: a configuration restored from FAILURE re-enters a cautious DEGRADED state until a subsequent send actually succeeds, at which point it recovers to OK.
  • MISSING — provisioning failed; re-provision cleans any partial Postmark resources (404 = success), then re-runs the forward sequence with the deterministic server name (ardamails-{partition}-{slug}). A createServer 422 after cleanup is a drift/orphan condition routed to operator diagnosis.
  • UNVERIFIED — verification gave up; re-verify restarts the poll with a fresh window.
  • FAILURE — repeated send failures crossed the soft threshold; operator triage, then recovery to DEGRADED.

States: UNLOCKED, LOCKED, LOCKING. Newly-created configurations are locked-by-default (created PENDING ∥ LOCKED); an operator unlock — typically after verification — makes the configuration actually sendable.

  • lock — graceful: waits for in-flight sends to finish (UNLOCKED → LOCKING → LOCKED), then blocks new sends.
  • force-lock — immediate: cancels in-flight sends through the shared in-flight tracker and goes straight to LOCKED.
  • unlock — returns to UNLOCKED.

A LOCKING configuration left mid graceful-drain by a crashed process is completed by a startup reconciliation pass.

The provisioning entry action and the verification poll, end to end:

PlantUML diagram