Skip to content

Decision Log: Invitation Mail Server

Tracks design decisions for the invitation-mail-server project (goal, PDEV-1235): provisioning a partition-level system email server and delivering the common-module email-sending library. Decisions inherited from the email-integration project (DQ-009, DQ-R1-009, DQ-R1-017, DQ-R1-022, secret-delivery pattern) are constraints here, not re-opened.

#QuestionStatusDecisionRound
DQ-001Sender Signature for system.{partition}?DecidedA — inherit the partition SignatureR1
DQ-002System server token custody between deploysDecidedB — partition-vault 1Password itemR1
DQ-003CDK placement of new resourcesDecidedA — extend PartitionEmailStackR1
DQ-004Source of the library sending classesDecidedA — lift & generalize from operations; it adoptsR1
DQ-005How components obtain a configured senderDecidedA′ — component email block; server-agnostic libraryR2
DQ-006operations wiring scope in this projectDecidedA — secret projection + internal system senderR2
DQ-007Webhook posture for the system server (v1)DecidedA — none (fire-and-forget); console follow-up filedR2

DQ-001: Sender Signature for system.{partition}?

Section titled “DQ-001: Sender Signature for system.{partition}?”

Context: The system server sends from system.{partition}.{base}. Postmark verification per DQ-R1-009 (email-integration) anchors Sender Signatures at a parent domain, with leaf sub-domains inheriting DKIM. The partition Signature at {partition}.ardamails.com already exists (Phase 4); tenant leaves ride it (DQ-R1-017).

OptionDescriptionTrade-offs
AInherit the partition Signature: send from system.{partition}.{base} with DKIM/Return-Path resolved at the partition parent.Zero new DNS records, zero new signature registrations, no placeholder-context extension, minimal drift surface. System mail shares receiver-side reputation with tenant mail on the same partition.
BDedicated Signature at system.{partition} with its own DKIM TXT + Return-Path CNAME at the leaf.Reputation isolation between administrative and tenant mail; costs a second Pre-Deploy registration, new DNS records in the partition zone, placeholder-context machinery extension, and added drift checks.

Recommendation: Option A — consistent with how every tenant leaf already sends; reputation isolation has no evidence of need yet.

Decision: Option A. Reopen (→ Option B) if system-mail deliverability measurably suffers from tenant traffic on a shared partition Signature, or when the System Administration console (PDEV-1250) adds per-stream reputation monitoring that shows divergence.

Applied to:

  • Design Document § Structural Design, § Behavioral Design (Provisioning)
  • Goal § Constraints (domain fixed as system.{partition}.{base})

DQ-002: System server token custody between deploys

Section titled “DQ-002: System server token custody between deploys”

Context: Unlike the Postmark account token (operator-provisioned in 1Password, flowing 1P → NoEcho → SM), a Postmark server token is generated by Postmark at server creation. Something must custody it so subsequent deploys can (re)deliver it to the SM secret. Corporate precedent (corporate-cli, DQ-R1-007): persist to a 1Password vault item.

OptionDescriptionTrade-offs
ASM-only: Pre-Deploy re-reads ApiTokens[0] via GET /servers (using the account token it already holds) on every run and feeds the NoEcho parameter directly.No new 1Password items; Secrets Manager is the single custody point. But the token is invisible to operators outside AWS, rotation is implicit, and every deploy depends on a live Postmark API read.
BPartition-vault 1Password item: Pre-Deploy writes the token to the partition’s Arda-{Env}OAM vault at creation; deploys read 1P → NoEcho → SM like the account token.Operator-visible custody and explicit rotation surface; symmetric with every other partition credential (vault convention: service-name-only item title, one item per env vault). Costs a new item in all four vaults and a second custody point to keep consistent.

Recommendation: Option A — fewer moving parts, single custody point.

Decision: Option B (user override of the recommendation): the corporate precedent and the partition-vault convention win — operator visibility and an explicit rotation surface are worth the second custody point. New item title SystemEmailServer (field credential) in each Arda-{Env}OAM vault, added to all four per the vault convention; canonical op:// mapping lives in platforms.ts (PartitionMail), never in bash.

Applied to:

  • Design Document § Structural Design (PartitionMail, Pre-Deploy tool), § Behavioral Design (Provisioning), § Operations Impact
  • Goal § Success Criteria 1 (custody path)

Context: The new SM secret ({fqn}-I-EmailSystemServerToken), its NoEcho parameter, and its -API- export need a stack. PartitionEmailStack ({fqn}-Email) already owns all partition mail resources; CFN stack names are immutable (workspace rule), and amm.sh already runs the --force/NoEcho parameter dance for this stack.

OptionDescriptionTrade-offs
AExtend PartitionEmailStack with the additional parameter, secret, and export.One email stack per partition; rides the existing deploy path; cdk diff clean-or-additive. Couples system-email lifecycle to the existing stack (acceptable: same domain, same operator flow).
BNew sibling stack {fqn}-SystemEmail.Isolated lifecycle/blast radius; but duplicates parameter plumbing, adds amm.sh ordering, and fragments the email provisioning story for no current benefit.

Recommendation: Option A.

Decision: Option A. Reopen if system-email infra grows resources with a materially different lifecycle (e.g., its own zone or per-stream infrastructure).

Applied to:

  • Design Document § Structural Design (PartitionEmailStack), § Implementation Artifacts

DQ-004: Source of the library sending classes

Section titled “DQ-004: Source of the library sending classes”

Context: operations already contains a generic, stateless Postmark sending client (PostmarkServerProxy.sendEmail, sealed SendEmailOutcome, wire types) and a retry core (EmailSender) entangled with its tenant ConfigurationService. The common-module library needs exactly this behavior.

OptionDescriptionTrade-offs
ALift & generalize: move the wire types, send proxy, sealed outcome, retry core (parameterized on a token supplier + optional lifecycle hooks), and fromHeader into common-module; operations re-imports them for the per-tenant path.Single implementation; no divergence between the system and per-tenant senders; the retry core’s tenant coupling stays behind an adapter in operations. Medium operations diff, well covered by existing unit/integration tests.
BFresh library classes; operations keeps its private copy.Zero operations regression risk in this project, but two Postmark send clients drift apart; the accounts follow-up inherits the dedup debt.

Recommendation: Option A.

Decision: Option A. The lift preserves public behavior; operations’ existing email tests are the regression net.

Applied to:

  • Design Document § Structural Design (library elements), § Implementation Artifacts (common-module, operations)

DQ-005: How components obtain a configured sender

Section titled “DQ-005: How components obtain a configured sender”

Context: The library must serve two construction paths: the system sender (config/secret-driven, this project) and operations’ per-tenant senders (DB-driven EmailConfiguration). ConfigurationProvider already demonstrates the component-level binding pattern (globalDsConfiguration: optional top-level HOCON block, lazily bound, smart-constructed with Result).

OptionDescriptionTrade-offs
AComponent-level email block: ConfigurationProvider binds an optional top-level email {} HOCON block via EmailConfig.fromConfig(): Result<EmailConfig>; token value reaches HOCON through the existing arda.config.location secret-file layering.System email becomes a first-class component capability; accounts adoption ≈ config + one call. Adds a binding to ConfigurationProvider (small, optional-block precedented).
BConsumer-wired factory: library ships pure classes; each component reads its secret mount and wires the sender in its composition root (like TokenCipher material today).No ConfigurationProvider change, maximal flexibility; every adopter re-implements boot wiring.

Recommendation: Option A.

Decision: Option A with a clarification (A′): the library is email-server-identity-agnostic. It provides the classes to send email given a configured email server — a server-connection value type (API base, server token, from-domain parameters), a factory that builds a sender from that value, and the config binding that produces the value from HOCON. The same server value type is constructible from operations’ DB-based EmailConfiguration, so the config-bound path (system) and the DB-bound path (per-tenant) are two producers of one input type. No server identity, domain literal, or account knowledge is baked into common-module.

Applied to:

  • Design Document § Structural Design (library API), § Behavioral Design (Construction paths), § Module-Interaction Mechanics

DQ-006: operations wiring scope in this project

Section titled “DQ-006: operations wiring scope in this project”

Context: The accounts invitation flow adopts the capability in a follow-up project. This project must decide how much runtime consumption operations ships now, given success criterion 2 (integration-tested sending path) and the risk of provisioning a secret nothing consumes.

OptionDescriptionTrade-offs
ASecret + internal sender: project the system-token secret (ExternalSecret entry + mount), bind the email block, construct the system EmailSender at boot, expose it internally (no REST).Proves infra → secret → config → send end-to-end in a deployed component; dev-partition smoke can send real mail via PostmarkNonProd. Modest, additive diff.
BNone: library proven by common-module tests only.Smallest diff; but the provisioned secret ships unconsumed and the seam is first exercised by the follow-up.
CA + a guarded diagnostic REST endpoint for test sends.Best operability; adds API surface and auth review this project doesn’t need.

Recommendation: Option A.

Decision: Option A. The diagnostic surface is deferred to the System Administration console (PDEV-1250).

Applied to:

  • Design Document § Structural Design (operations wiring), § Behavior Verification (E2E), § Operations Impact

DQ-007: Webhook posture for the system server (v1)

Section titled “DQ-007: Webhook posture for the system server (v1)”

Context: The existing postmark-events webhook route is tenant-scoped by construction (opaque routing token → tenantId → tenant-owned rows). A system server would need a non-tenant scope to reuse it.

OptionDescriptionTrade-offs
ANo webhook — fire-and-forget: system sends carry no bounce/complaint ingestion; delivery issues observed via Postmark console + logs/metrics. Per-message status remains interrogable on demand via the server-token Messages API (GET /messages/outbound/{messageid}/details).No changes to tenant-scoped webhook machinery; monitoring debt explicitly parked on the console follow-up.
BInstall a webhook to a new system scope now: mint a system routing token, extend the resolver and record* paths beyond tenant scope.Bounce/suppression tracking from day one, but touches the tenant-scoped machinery this project otherwise avoids, before a real consumer exists.

Recommendation: Option A.

Decision: Option A, plus a filed follow-up: PDEV-1250 — System Administration console for system email capability (low priority) covers capability management, sent-mail monitoring, and the webhook-vs-polling ingestion decision.

Applied to:

  • Design Document § Behavioral Design (Robustness), § Operations Impact, § Out of Scope


Copyright: (c) Arda Systems 2025-2026, All rights reserved