Phase 5b -- Pre-Existing Decisions
Phase 5b implements the ShopAccess/Email module inside the operations component: per-tenant Postmark server provisioning, sending APIs, bounce / complaint webhook handling, suppression-list maintenance, and the TokenCipher / EmailConfigurationService infrastructure. It consumes the per-partition resources that Phase 4 lays down and the cross-cutting helpers that Phase 5a contributes.
This file enumerates every prior decision Phase 5b must honor, organised by source. When Phase 5b’s goal.md and design artifacts are written, they reference these decisions rather than re-deriving the underlying constraints. This is a reference document, not a planning artifact.
Project goal constraints
Section titled “Project goal constraints”From ../goal.md:
Tenant provisioning and lifecycle
Section titled “Tenant provisioning and lifecycle”- A new tenant is configured with a dedicated sending domain; DNS records are requested, published, and verified automatically.
- Email sending is gated by DKIM + Return-Path verification of the tenant’s sending domain.
- Tenant email configuration can be locked, retried, or removed through the platform’s standard API surface — no ad-hoc operator action.
Sending and delivery tracking
Section titled “Sending and delivery tracking”- A consumer requests an email send; the request produces a delivery attempt observable end-to-end.
- Delivery, bounce, and spam-complaint events from Postmark are received, authenticated, and reflected in the recorded job status.
- Transactional-only. The
EmailSenderL2 interface accepts transactional sends exclusively (1-to-1, user-action-triggered, non-promotional). Every Postmark server provisioned for Application Runtime tenants uses the default Transactional Message Stream. Bulk / broadcast / marketing sends are out of scope for the Phase 5b interface; any future broadcast use case requires a separate Postmark Broadcast Message Stream and a distinct L2 API. Constraint recorded in cross-cutting-design § 7a and the Postmark operator runbook; it derives from Postmark policy and is reinforced as a deliverability prerequisite in Postmark’s account-approval correspondence.
Free Kanban Tool
Section titled “Free Kanban Tool”- The Free Kanban Tool (Phase 3 deliverable) sends transactional email from
freekanban.arda.ardamails.com. Phase 5b’s email module does not consume the Free Kanban Tool’s flow; the Free Kanban Tool has its own runtime resolution path. The Phase 5b module is for Application Runtime tenants.
Operability
Section titled “Operability”- Periodic drift detection asserts external resources match the declared state. Phase 5b adds module-side drift to the existing
corporate-drift(Corporate) +runtime-platform-drift(Phase 4) workflows: e.g., asserting that every persistedemail_configurationrow’skeyVersionIdis reachable in the operations component pod’s key registry. - Aggregate operational management uses the Postmark Console (no Arda-side OAM surface for the email service).
Architectural constraints
Section titled “Architectural constraints”From ../architecture-overview.md:
- Application-layer is four sub-layers (L1 / L2 / L3 / L4 per DQ-201).
- L1 — Protocol proxies: stateless wrappers around external HTTP / SDK APIs. One per external surface:
postmarkAccountProxy,postmarkServerProxy,route53ZoneProxy. One credential strategy per proxy.runCatching-bodied methods returningResult<T>. - L2 — Capability composers: stateless. Choreograph L1 calls into capability operations (
provision,decommission,verifyDns,sendOne). Map external errors to capability errors. No DB access. - L3 — Application services: stateful. Hold DB access via
DataAuthority. Hold the encryption key (DQ-202 / DQ-203). Encrypt before persist; decrypt on demand. Spawn bounded DNS-verification polling rounds via per-podactivePollingmap (DQ-207). - L4 — HTTP endpoints: REST entry points:
email-configuration,email-job,postmark-events(webhook).
Round 1 decisions (DQ-001..013)
Section titled “Round 1 decisions (DQ-001..013)”| Decision | Subject | Phase 5b-relevant constraint |
|---|---|---|
| DQ-001 | Tenant sending-domain shape {tenant}.{partition}.{mail-root-domain} uniformly | The Email module must produce / consume FQDNs in this shape; no special-casing for prod vs non-prod |
| DQ-002 | Multi-config strategy: {conf-slug}.{tenant}.{partition}.{mail-root-domain} sub-subdomain (v2+) | v1 provisions at the tenant level only; schema includes nullable config_slug for future extension |
| DQ-003 | Tenant slug source from provisioning request | The provisioning API contract must accept tenantEId / tenantName / tenantSlug |
| DQ-004 | Reply-To not user-editable | Send dialog and BFF route honor this; system-resolved from procurement contact or user email |
| DQ-005 | Email order send paths: copy-paste + system both coexist | Send service supports both paths |
| DQ-006 | CS alerting via ESP OOTB only in v1 | No Arda-built alerting in Phase 5b |
| DQ-007 | Document generation owned by calling feature | Email module accepts Blob/URL; does not generate documents |
| DQ-008 | Single-step send dialog | UI-level (not Phase 5b directly) |
| DQ-009 | Mail root domain ardamails.com | All FQDN composition uses this constant |
| DQ-010 | Per-partition zone placement | Partition zone is the mail sub-zone; tenant records land within it |
| DQ-011 | Bearer-token webhook auth via Postmark modern Webhooks API | postmark-events route validates ARDA_API_KEY in Authorization: Bearer … |
| DQ-012 | Per-tenant server token encrypted in DB (application-level) | EmailConfigurationService encrypts before persist; decrypts on demand. See DQ-R1-019 for the encryption-key design. |
| DQ-013 | IAM role stays in RootDnsStack | Not extracted; informs Phase 4’s IAM-role design but not Phase 5b directly |
Application-layer decisions (DQ-201..208)
Section titled “Application-layer decisions (DQ-201..208)”These pin the module’s internal structure. Phase 5b must implement against these:
| Decision | Subject | Phase 5b implementation note |
|---|---|---|
| DQ-201 | L1/L2/L3/L4 sub-layers | Module package structure matches this layering; no upward dependencies |
| DQ-202 | AES-256-GCM versioned envelope | Updated by DQ-R1-019 to a two-axis envelope a{N}.k{SM-VERSION-ID} |
| DQ-203 | HKDF derivation, info = "arda.email.serverToken.a{N}" | Phase 5b implements EnvelopeAlgorithm.deriveKey per a{N} |
| DQ-204 | STS role chain (15-min session) for outbound AWS | route53ZoneProxy (L1) uses STS-chained credentials |
| DQ-205 | Persist-first lifecycle | email_configuration rows are written before external provisioning calls |
| DQ-206 | Encryption key held by L3 service only | TokenCipher injected at L3; never reaches L2 or L1 |
| DQ-207 | Per-pod bounded DNS-verification polling | L3 holds the activePolling map; bounded retries |
| DQ-208 | Async-tx boundaries owned by L3 | L1 / L2 are stateless; L3 owns the transaction boundary |
Round R1-Phase1 decisions (DQ-R1-001..007)
Section titled “Round R1-Phase1 decisions (DQ-R1-001..007)”| Decision | Subject | Phase 5b note |
|---|---|---|
| DQ-R1-001 | external-resources-drift.yml filename | Phase 5b’s module-side drift extends the same naming convention if it ships drift checks |
| DQ-R1-002 | tools/drift-check.ts location | Same convention applies if Phase 5b ships a module-side drift driver |
| DQ-R1-003..005 | Operator runbook conventions | Phase 5b’s runbook follows the same Markdown sign-off format |
| DQ-R1-006 | Cross-zone NS-delegation locus | Phase 5b’s tenant DNS provisioning writes upstream into the partition zone via the existing WriteNSRecordsToUpstreamDns pattern |
| DQ-R1-007 | Arda-CorporateOAM vault separation for Free Kanban Tool token | Phase 5b’s per-tenant token storage is in DB (DQ-012), not 1Password — DQ-R1-007 is informational only |
Round R1-Phase2 decisions (DQ-R1-008)
Section titled “Round R1-Phase2 decisions (DQ-R1-008)”| Decision | Subject | Phase 5b note |
|---|---|---|
| DQ-R1-008 | cdk import for the existing ardamails.com zone | Already adopted; informational |
Round R1-Phase3 decisions (DQ-R1-009..016)
Section titled “Round R1-Phase3 decisions (DQ-R1-009..016)”| Decision | Subject | Phase 5b note |
|---|---|---|
| DQ-R1-009 | Postmark domain verification at the parent (Corporate scope) | Phase 5b’s per-tenant verification follows the per-tenant pattern (each tenant is its own Sender Signature); the parent-verification pattern applies to Phase 4 partition Signatures, not to per-tenant ones |
| DQ-R1-010 | NS-delegation write through WriteNSRecordsToUpstreamDns even when same-account | Phase 5b’s per-tenant DNS writes use the same pattern |
| DQ-R1-011 | dns-zone.ts construct rename | Phase 5b can consume the renamed construct |
| DQ-R1-012 | corporate-drift.yml scope | Phase 5b does not modify the Corporate drift; Phase 5b’s module-side drift is separate (see Phase 4 runtime-platform-drift) |
| DQ-R1-013 | Phase A failure ordering for Postmark server token | Phase 5b’s per-tenant server creation follows the same in-memory buffer + retry pattern adapted to L1/L2/L3 |
| DQ-R1-014 | cdk.context.json commit policy | Phase 5b does not write to cdk.context.json; CDK-side concern only |
| DQ-R1-015 | DMARC mailbox dmarc-reports@arda.cards | DMARC reports for the partition mail tree route here; informational |
| DQ-R1-016 | Reserved-name registry at arda.ardamails.com | The per-tenant slug-validation logic in Phase 5b must reject reserved names |
Round R1-Phase4 decisions (DQ-R1-017..022) and the Phase 5b open question (DQ-R1-023)
Section titled “Round R1-Phase4 decisions (DQ-R1-017..022) and the Phase 5b open question (DQ-R1-023)”| Decision | Subject | Phase 5b implementation note |
|---|---|---|
| DQ-R1-017 | One Postmark Sender Signature per partition; leaves inherit DKIM | Phase 5b’s per-tenant sub-domains inherit DKIM via the partition’s signing key by default. Whether per-tenant Signatures are introduced is the separate open question DQ-R1-023 (below). |
| DQ-R1-018 | Parallel runtime-platform-drift workflow | Phase 5b’s module-side drift extends runtime-platform-drift, sharing the reusable scripts established in Phase 4 |
| DQ-R1-019 | Per-partition email server-token encryption key (two-axis envelope, hot-swap, lazy + coroutine migration, SDK fallback) | Major Phase 5b deliverable. TokenCipher implements the dispatch model; EmailConfigurationService (L3) drives the lazy migration coroutine; Helm chart declares two ExternalSecret resources for AWSCURRENT and AWSPREVIOUS (both referencing the SM ARN from ${publishingPrefix}-API-EmailEncryptionKeyArn); the operations pod STS-chains into EmailEncryptionKeyFallbackRole (not the pod’s own IRSA role directly) for SDK cache-miss fetches. Full design in ../4-runtime-platform-updates/design/email-server-key-encryption.md. |
| DQ-R1-020 | DNS-provisioning + SM-fallback IAM roles | Resolved. Two STS-chained per-purpose roles per partition; DNS-records role via reuse of the generalized AllowCreatingNSRecordsRole construct (Root no-drift guarded). Phase 5b consumes the role ARNs via the ${publishingPrefix}-API-EmailDnsProvisioningRoleArn and ${publishingPrefix}-API-EmailEncryptionKeyFallbackRoleArn CFN exports, threaded into Helm values by amm.sh (Phase 4 specifies the exact wiring). |
| DQ-R1-021 | Partition rollout order dev → stage → demo → prod; kyle excluded | Phase 5b deploys to one partition at a time in this order |
| DQ-R1-022 | Operator CLI integrated into amm.sh, no standalone partition-mail-cli | Phase 5b’s tenant-provisioning admin tooling extends amm.sh (or its callees) rather than adding a sibling CLI. Reusable utilities are shared with corporate-cli (extracted by Phase 4). |
| DQ-R1-023 | Per-tenant Postmark Sender Signature introduction | Open — to be confirmed at Phase 5b planning. Four options (α status quo / β per-tenant from v1 / γ hybrid opt-in / δ remediation-only). If Phase 5b adopts β / γ / δ, the runtime tenant-onboarding flow registers per-tenant Sender Signatures via the Postmark Account API and writes per-tenant DKIM TXT + Return-Path CNAME records into the partition mail sub-zone using the EmailDnsProvisioningRole (provisioned by Phase 4 specifically for this purpose). If α, the role is unused in v1 but available for later introduction with no re-work. Decision should be informed by pilot-phase tenant volume + bounce/spam data, Postmark guidance at the time, and any tenant contractual reputation-isolation requirements. Full text in ../decision-log.md#dq-r1-023. |
Phase 4 deliverables Phase 5b consumes
Section titled “Phase 4 deliverables Phase 5b consumes”Phase 5b deploys on top of Phase 4’s per-partition surface. Each item below is a Phase 4 output Phase 5b reads / mounts / invokes:
| Phase 4 deliverable | Phase 5b consumption |
|---|---|
Per-partition mail sub-zone ({partition}.ardamails.com) | Per-tenant sub-domain records are created within this zone by the L1 route53ZoneProxy |
WriteNSRecordsToUpstreamDns construct | Per-tenant DNS writes use this pattern (DQ-R1-010) |
Per-partition Postmark account-token SM secret ({fqn}-I-EmailPostmarkAccountToken) | Read via the existing extras.email.postmarkAccountToken HOCON path |
Per-partition encryption-key SM secret ({fqn}-I-EmailEncryptionKey) | Read via two ExternalSecret mounts (AWSCURRENT + AWSPREVIOUS); fed into TokenCipher |
| Per-partition DNS-provisioning IAM role | Pod IRSA assumes this role for tenant DNS writes |
Partition-aware Postmark credential accessor in platform/postmark-service.ts | Deploy-time tooling resolves the credential reference per partition |
Per-partition Sender Signature at {partition}.ardamails.com | Per-tenant leaves inherit DKIM from this Signature |
Cross-cutting design constraints
Section titled “Cross-cutting design constraints”From ../cross-cutting-design.md:
- Threat model: Phase 5b owns DB-exposure defense via DQ-012 + DQ-R1-019 (application-level encryption). Pod / process compromise is platform-level (not Phase 5b’s scope).
- Webhook auth:
postmark-eventsroute validatesAuthorization: Bearer ARDA_API_KEYin-component (no API-Gateway authorizer). - Outbound auth:
- Postmark Account API uses
X-Postmark-Account-Tokenfrom per-partition SM secret via ESO. - Postmark Server API (per-tenant) uses the per-tenant decrypted token, passed in-memory through L2 / L1; never logged.
- Route53 uses STS-chained credentials (15-min sessions) per DQ-204.
- Postmark Account API uses
- Secret-handling rules apply to Phase 5b code:
- Plaintext server tokens never logged.
- Encryption key never logged.
- Field-by-field log helpers exclude
serverTokenEncryptedand any plaintext.
- Drift detection: Phase 5b extends the partition-side
runtime-platform-driftworkflow with module-level assertions (e.g., every persistedkeyVersionIdis reachable; orphan Postmark servers vs DB rows).
Component vs Module terminology
Section titled “Component vs Module terminology”operationsis a component (the Kotlin/Ktor runtime deployable, single Helm release per partition).ShopAccess/Emailis a module (a functional element insideoperations, communicating with other modules via Kotlin DI service interfaces).- The IRSA role, the pod’s identity, the Helm release, the
ExternalSecrets — all are properties of the component. - The
TokenCipher,EmailConfigurationService,EmailJobService,EmailSender, the L1 / L2 / L3 / L4 layering — all are properties of the module.
Phase 5b’s docs should preserve this distinction.
Other artifacts to consult during Phase 5b planning
Section titled “Other artifacts to consult during Phase 5b planning”../goal.md— project-level acceptance criteria, the canonical statement of what Phase 5b must deliver at capability level.../phases.md§ Phase 5b — phase scope, exit criteria, deploy order, recovery.../architecture-overview.md— runtime topology, IaC code hierarchy, instance groups, application-layer (§ 7).../cross-cutting-design.md— auth, secrets, drift, OAM, compliance.../decision-log.md— full text of every DQ referenced above.../4-runtime-platform-updates/design/email-server-key-encryption.md— the canonical encryption design (Phase 5b implements its application-side dispatch and migration model).../current-system-retrofit.md— pages Phase 5b will retrofit at project completion (email-module.md,email-configuration-lifecycle.md,email-job-lifecycle.md,email-implementation-notes.md).../3-corporate-updates/implementation/learnings.md— Phase 3 learnings (especially L-1 “Design decisions that constrain code must be encoded as code”) apply to Phase 5b’s design discipline.../3-corporate-updates/implementation/suggestions.md— forward-looking items.- The
operationsandcommon-modulerepositories’knowledge-base/directories for repo-local conventions.
Copyright: (c) Arda Systems 2025-2026, All rights reserved
Copyright: © Arda Systems 2025-2026, All rights reserved