Email Integration -- Architecture Overview (Revision 1)
Top-level architecture and runtime topology for the Email Integration project, restructured under the runtime-design principles adopted in Runtime Overview.
Revision 1 note. This document supersedes the prior architecture overview (now superseded by this document). Substantive architectural changes:
- DNS topology re-shaped: Corporate gets a dedicated
arda.ardamails.comzone, separate from the partition mail sub-zones used by Application Runtimes. Free Kanban Tool sends fromfreekanban.arda.ardamails.com.- IaC code organisation follows the Constructs / Stacks / Apps hierarchy with the
platform/vsinstances/distinction.- Postmark integration adopts a
platform/constructs/postmark/thin-wrapper pattern; per-resource creation in CDK proceeds via Custom Resources (target) or via Corporate-CLI two-phase orchestration (interim).- Instance groups: Application Runtimes, Platform-level (Root, OAM), and Corporate are first-class peers.
The build plan has been re-numbered; see
phases.mdfor the build sequence.
1. Goal and scope
Section titled “1. Goal and scope”The project integrates the Postmark ESP into the Arda platform and delivers the ShopAccess/Email module — a general-purpose backend capability for sending transactional email on behalf of tenants, with per-tenant domain isolation, delivery tracking, and secure credential management. See goal.md for the full scope.
In scope:
- External-resource references (Postmark accounts, 1Password vault items) declared under
platform/. - DNS infrastructure: root zone (
ardamails.com), Corporate zone (arda.ardamails.com), per-partition mail sub-zones ({partition}.ardamails.com). - Corporate instance assets, beginning with the Free Kanban Tool (
freekanban.arda.ardamails.com). - Per-partition email infrastructure: secrets, IAM roles, NS delegations.
- Postmark API thin-wrappers at
platform/constructs/postmark/(Construct-line shape). - The
ShopAccess/Emailmodule within theoperationscomponent (L1 / L2 / L3 / L4). common-moduleadditions (sanitizeHeader,AppError.Application, etc.).- Async DNS verification, encrypted server-token storage, webhook event handling.
Out of scope:
- Frontend (SPA, BFF) — consumed by a downstream project.
- Procurement-specific use cases (email orders, purchase order by email).
- HTML email template rendering for specific business entities.
- Migration of
amm.shordeploy-root.shto the principle-aligned operator-script shape (deferred perruntime-design-review.md§ 2).
2. The two perspectives: IaC vs Runtime
Section titled “2. The two perspectives: IaC vs Runtime”Per the runtime-design principles, the infrastructure repository is the single reference for every resource the platform depends on. Two perspectives organise the work:
| Perspective | What it is | Where it lives |
|---|---|---|
| IaC | Code artifacts that define resources | infrastructure/src/main/cdk/ (TypeScript), infrastructure/tools/ (operator-facing CLIs and utilities), infrastructure/.github/workflows/ (CI / drift), plus the existing repo-root scripts (amm.sh, deploy-root.sh) |
| Runtime | Live resources, instantiated from the IaC artifacts | AWS (CDK / CFN), Postmark (API-driven), 1Password (referenced), GitHub (Actions secrets), Kubernetes (Helm) |
Each runtime resource has at most one IaC owner; references go the other direction (an IaC artifact may reference an external runtime resource that Arda does not create — e.g., the Postmark account itself).
The four IaC categories from the principles map to this project as follows:
| Category | Examples in this project |
|---|---|
| AWS resources managed by Arda | Route53 hosted zones, Secrets Manager entries, IAM roles, CloudWatch alarms |
| External resources Arda consumes but does not create | PostmarkProd / PostmarkNonProd accounts, Arda-SystemsOAM 1Password vault, Arda-cards GitHub organisation, ardamails.com domain registration |
| Resources in third-party services Arda creates and maintains via IaC | Postmark servers + sending domains (per Free Kanban Tool, per tenant), GitHub Actions secrets |
The high-level relationship between IaC artifacts and the runtime instances they create:
The detailed runtime / IaC composition is shown in § 5 (where the Corporate instance group is the worked example).
3. Architectural dependencies
Section titled “3. Architectural dependencies”The static dependency graph among the project’s architectural elements. Each row reads “the element on the left needs the element on the right to exist or be reachable.” These are dependencies of structure, not of build order; the temporal sequencing in which they are delivered is in phases.md.
| Element | Depends on |
|---|---|
apps/Corporate/ | platform/postmark-service.ts (PostmarkProd account ref); platform/one-password.ts (vault refs); arda.ardamails.com zone is delegated by Root |
CorporateMailDns stack | constructs/xgress/dns-zone.ts; the ardamails.com parent zone existing in Root |
FreeKanbanToolMailDns stack | CorporateMailDns stack outputs (zone reference); platform/constructs/postmark/ thin-wrapper constructs |
Per-partition mail stacks (apps/Al1x/...) | platform/postmark-service.ts (per-partition Postmark account ref); the partition’s mail sub-zone is delegated by Root |
operations email module | Per-partition Secrets Manager entries (Postmark account token, encryption key); per-partition IAM provisioning role; common-module library APIs |
Root (apps/Root/) | platform/postmark-service.ts and platform/one-password.ts reference files (used to keep declaration shape consistent across instances) |
| Postmark thin-wrapper constructs | platform/postmark-service.ts (account refs); 1Password vault for credentials; offline test fixtures for unit testing |
| Drift-detection workflows | OP_SERVICE_ACCOUNT_TOKEN GitHub Actions secret; the runtime resources whose state they assert |
Runtime instances sit downstream of these dependencies in the obvious way: a Route53 zone exists once its NS delegation is in place; a Postmark server exists once its account-token credential is reachable; an operations pod can send email once its partition’s secrets are projected through ESO and its IAM provisioning role is assumable.
4. DNS topology
Section titled “4. DNS topology”The mail-domain hierarchy is reorganised so Corporate and Application Runtime traffic do not share zones.
Properties of the layout:
- Root owns
ardamails.comonly. NS records there delegate all sub-zones; SPF / DMARC for the whole tree are anchored there. - Corporate owns
arda.ardamails.com(lives in the Root AWS account for now; see § 6.4). Sub-domains (e.g.,freekanban.arda.ardamails.com) host DKIM and Return-Path for individual Corporate Postmark servers. - Application Runtime partitions own
{partition}.ardamails.comzones in their own AWS account (Alpha001 for prod / demo, Alpha002 for dev / stage, SandboxKyle002 for kyle). Per-tenant sending sub-domains are provisioned at runtime by the operations component’s email service. - No cross-instance dependency between Corporate and Application Runtimes. Free Kanban Tool’s DNS records are entirely within Corporate’s zone; partition zones are entirely within partitions.
5. IaC code hierarchy
Section titled “5. IaC code hierarchy”Source: Runtime Overview § 6. The general layered-architecture rules — dependency direction, layer responsibilities, and reuse model — are documented under IaC Functional Design; this section pins those rules to the email-integration project’s concrete additions.
infrastructure/├── amm.sh # operator script -- Application Runtimes (existing, unchanged)├── deploy-root.sh # operator script -- Root (existing; minimal path-only edit)├── tools/ # operator-facing CLIs and ad-hoc utilities│ ├── corporate-cli.ts # Corporate CLI: orchestrates Phase A (Postmark) + Phase B (CDK)│ └── gha-secret.ts # transition-state ad-hoc CLI for GHA-secret provisioning├── src/main/cdk/│ ├── apps/ # CDK App entry points (one per concrete instance)│ │ ├── Al1x/ # Application-Runtime apps│ │ ├── Corporate/ # Corporate apps (free-kanban-tool, future hubspot, ...)│ │ └── Root/ # Root apps (folder renamed from rootConfiguration/; CFN stack name preserved)│ ├── instances/ # Concrete instance declarations (under repo control)│ │ ├── Alpha001/ Alpha002/ SandboxKyle002/ # Application Runtimes│ │ ├── Corporate/ # free-kanban-tool.ts and future siblings│ │ └── Root/ # ardamails.com zone declaration + delegation table│ ├── platform/ # Decoupled config / external "magic" / pre-defined patterns│ │ ├── postmark-service.ts # PostmarkProd / PostmarkNonProd account refs│ │ ├── one-password.ts # OAM_VAULT and other 1P references│ │ ├── ari-configuration.ts # MAIL_RESERVED_SLUGS and related platform-wide naming│ │ └── constructs/ # thin wrappers for non-AWS service APIs (one folder per provider, lowercase)│ │ └── postmark/ # PostmarkServer, PostmarkSendingDomain│ ├── stacks/│ │ ├── corporate/ # corporate-mail-dns.ts, free-kanban-tool-mail-dns.ts│ │ ├── infrastructure/ # per-Infrastructure stacks│ │ ├── purpose/ # per-Partition stacks (incl. partition-email.ts)│ │ └── root/ # Root DNS stacks│ ├── constructs/│ │ ├── compute/ network/ oam/ storage/ # functional categories for AWS-resource constructs│ │ └── xgress/ # ingress / egress -- DNS, API gateway, etc.│ │ ├── dns-zone.ts # generalised hosted-zone construct│ │ ├── dns-email-records.ts # DKIM TXT + Return-Path CNAME (generic for any sending sub-domain)│ │ └── (existing xgress constructs)│ └── utils/ # cross-cutting TS utilities (logging + redact, 1Password SDK wrapper, ...)└── .github/workflows/ # CI + drift detectionNotes on placement:
- Operator scripts: existing
amm.sh(Application Runtimes) anddeploy-root.sh(Root) stay at the repo root. New TypeScript launchers live undertools/(one entry point per class of resource, matching the established granularity). - Cross-cutting utilities: the 1Password SDK wrapper, logging + redaction, and similar repo-wide helpers live under
src/main/cdk/utils/. Tools that need them import from there. Thegha-secret.tstool’s libsodium-and-Octokit logic lives insidegha-secret.tsitself (single-consumer, not promoted to a shared utility). - File-size discipline: TypeScript modules over ~500 lines should be split into a folder (
<module-name>/index.tsplus siblings), kept in the same target location.
5.1 Three types of construct
Section titled “5.1 Three types of construct”| Type | Path | Notes |
|---|---|---|
| AWS resources (CDK L1 / L2) | aws-cdk-lib plus Arda’s L2 / L3 in constructs/<category>/ | Existing pattern. Email-specific additions in xgress/. |
| Third-party API thin-wrappers | platform/constructs/<provider>/ | Construct-line shape: Configuration / Built types, leaner than full L2 CDK Constructs (no full type-system integration). Lowercase provider folder names. New in this project. |
| Helm releases (deferred) | TBD src/main/helm/ | Documented in the principles; implementation deferred per reviews/R1/runtime-design-review.md § 7 Q4. |
5.2 Stacks and Apps
Section titled “5.2 Stacks and Apps”Stacks group resources sharing a deploy lifecycle. The CorporateMailDns stack owns the arda.ardamails.com zone, plus its SPF and DMARC records:
- SPF at
arda.ardamails.com:v=spf1 include:spf.mtasv.net ~all(Postmark’s SPF include). - DMARC at
_dmarc.arda.ardamails.com: a Corporate-aggregate-report mailbox plus an initial monitoring policy (p=quarantine; sp=quarantine); the policy hardens top=rejectonce Free Kanban Tool sending has bedded in.
The FreeKanbanToolMailDns stack composes the email-records construct with the Postmark thin-wrapper construct; the Stack passes Built values from the Postmark wrapper to the records construct so neither construct depends on the other.
Per DQ-R1-009, DKIM TXT lives at the Corporate zone parent (arda.ardamails.com); Return-Path CNAME lives at the per-asset leaf (pm-bounces.freekanban.arda.ardamails.com). The function sendingDomainPlacement() in platform/constructs/postmark/sending-domain.ts is the typed source-of-truth that the Corporate CLI (Phase A), the CDK construct (Phase B), and the drift check all consume identically — so the three sides cannot drift apart by re-deriving the same value independently. The same pattern generalises to per-partition Sender Signatures in Phase 4.
The Corporate App (apps/Corporate/index.ts) defines the reusable CorporateApp class; the entry script tools/cdk-corporate.ts calls new cdk.App() and instantiates both stacks with names and configuration sourced from instances/Corporate/corporate.ts.
5.3 Postmark-server creation: interim and target
Section titled “5.3 Postmark-server creation: interim and target”The interim mechanism is in effect today; the target mechanism is the destination. The construct’s external surface is identical between the two; only its internals change. A decision-log entry (DQ-R1-NNN) records the rationale and migration trigger.
- Target mechanism: a CDK Custom Resource (Lambda-backed) inside
platform/constructs/postmark/PostmarkServer. The construct emits a custom resource into the CFN template; the Lambda calls Postmark on stack create / update / delete. A singlecdk deployinvocation creates everything declaratively. - Interim mechanism: the Corporate CLI orchestrates the work in two conceptual phases. Phase A invokes the Postmark thin-wrapper imperatively (creates / reconciles the Postmark server, captures DKIM and Return-Path public values into
cdk.context.json, writes the server token directly to 1Password). Phase B runscdk deploy; the stacks read the captured public values fromcdk.context.jsonand emit the DNS records. The two phases are conceptually two Apps deployed in sequence; no formal Apps are declared for them. CLI source comments name the phases explicitly so the eventual migration is unambiguous. - Value-transfer channel (interim):
cdk.context.json. Public values only (postmark.free-kanban.serverId,postmark.free-kanban.dkimSelector,postmark.free-kanban.dkimKey,postmark.free-kanban.returnPathTarget). The Postmark server token is the secret and never enters CDK context, file artifacts, or env vars in the deploy pipeline — it is written by Phase A directly to theFree-Kanban-Generator-Postmark-Server1Password item in theArda-CorporateOAMvault. - Migration trigger: when Lambda-backed Custom Resources become a wider repo pattern, the
PostmarkServerconstruct’s internals are swapped over and the CLI’s Phase A is retired. No caller code changes.
6. Instance groups
Section titled “6. Instance groups”The runtime is organised as three instance groups with distinct ownership conventions.
6.1 Application Runtimes
Section titled “6.1 Application Runtimes”Members: Alpha001 (prod, demo), Alpha002 (dev, stage), SandboxKyle002 (kyle). Each Infrastructure has its own AWS account and runs Arda’s commercial product. Mail sub-zones ({partition}.ardamails.com) live in the Infrastructure’s account; per-tenant sending sub-domains are runtime-provisioned by the operations component.
The application layer’s ShopAccess/Email module deploys into each partition’s existing operations component.
6.2 Platform-level
Section titled “6.2 Platform-level”- Root: shared resources that span the whole platform — the
ardamails.comzone, the existing root DNS structure, NS delegations to other instance groups. Lives inplatformRootAWS account. - OAM (Operate, Administer, Maintain): placeholder; not exercised by this project.
6.3 Corporate
Section titled “6.3 Corporate”Members: free-kanban-tool. Future: HubSpot integrations, marketing-website assets, other Arda-internal information systems.
The Free Kanban Tool sends from freekanban.arda.ardamails.com. Its Postmark server is created in PostmarkProd; its DKIM and Return-Path records are entirely within the Corporate zone. No cross-instance dependency on partition resources.
6.4 AWS-account-vs-instance-group decoupling
Section titled “6.4 AWS-account-vs-instance-group decoupling”Instance group is not the same as AWS account. Today: Application Runtimes ↔ Alpha001 / Alpha002 / SandboxKyle002 accounts; Root ↔ platformRoot account; Corporate ↔ platformRoot account (for now). Documentation must make this explicit. Future: Corporate may move to its own AWS account when its asset count justifies the operational separation; the trigger and migration mechanism are out of scope for this project but worth a one-line policy entry in the runtime IaC docs.
6.5 Reserved-name discipline
Section titled “6.5 Reserved-name discipline”Separation of Corporate domains under arda.ardamails.com adds a constraint: zone-names at the ardamails.com level (currently arda plus the partition names) must not collide with tenant slugs in any partition zone. The reserved-words list, in platform/ari-configuration.ts, is updated:
arda(and other future Corporate zone names) are reserved as zone names at theardamails.comlevel.- Existing partition reserved slugs (
mail,dmarc,postmaster,abuse,api,www,admin,arda,arda-cards,platform) remain reserved within partition zones for defence-in-depth. - Sub-zone slugs within
arda.ardamails.com(e.g.,freekanban, futurehubspot) follow the same uniqueness discipline; a Corporate-level registry is documentation-only at this stage.
7. Application layer (L1 / L2 / L3 / L4)
Section titled “7. Application layer (L1 / L2 / L3 / L4)”The application layer is structured into four sub-layers as the binding architectural decision (see DQ-201). Structure unchanged from the prior revision.
- 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 all 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).
Layer separation is more than packaging: L1 and L2 are stateless by contract, simplifying testing (mock at the layer boundary) and clarifying cross-pod behaviour (idempotent UPDATEs in L3 carry the cross-pod safety guarantees per DQ-207.e).
Relationship to IaC thin-wrappers. The
platform/constructs/postmark/thin-wrappers are the IaC-side analogue of L1 — structurally similar but used at deploy time, not runtime. Runtime calls (per-tenant server creation, per-tenant sending) traverse the Kotlin L1 proxies in the operations pod; deploy-time calls (the singleton Free Kanban Tool server) traverse the TypeScript thin-wrappers from the Corporate CLI / CDK Custom Resource.
7.1 Application-layer block diagram
Section titled “7.1 Application-layer block diagram”Detailed treatment of each layer will be documented in the phase specifications as each module is designed (see phases.md).
8. Runtime topology (operations component)
Section titled “8. Runtime topology (operations component)”This section is preserved from the prior revision; substantive content unchanged.
8.1 Component packaging
Section titled “8.1 Component packaging”The ShopAccess/Email module ships as part of the existing operations component — a Kotlin / Ktor application running on JVM 21 / Netty in EKS. No new component, no new deployable image. The module contributes:
- Source code under
cards.arda.operations.shopaccess.email(operations repo). - A small Helm chart contribution (
apis.system.shopAccess.emailentry; ESOExternalSecretentries). - A Flyway migration set under
shopaccess/email/database/migrations/. - API Gateway routes for the module’s L4 endpoints declared in the
operationsrepo’s CloudFormation files.
This rides the existing operations release cadence; no separate versioning.
8.2 Pod-level topology
Section titled “8.2 Pod-level topology”The operations component runs with two replicas in production. Implications:
- No leader election. Two replicas run identical code. Cross-pod coordination via idempotent state-guarded UPDATEs at the L3 boundary — e.g., transition to
UNLOCKEDis a no-op if the row is no longer inPENDING_VERIFICATION(DQ-207.e). - Per-pod state. Each pod maintains a
ConcurrentHashMap<UUID, Job>of in-flight bounded DNS-verification polling tasks (DQ-207.f). Pod restart drops in-flight tasks; recovery is trigger-driven. - Graceful shutdown. On
ApplicationStopping, L3 cancels allactivePollingjobs (DQ-207.k).
8.3 External system connections
Section titled “8.3 External system connections”| Direction | System | Protocol | Credential | Used for |
|---|---|---|---|---|
| Outbound | Postmark Account API | HTTPS | X-Postmark-Account-Token (per Postmark account; partition-wide) | Server / domain CRUD, DKIM / Return-Path verification |
| Outbound | Postmark Server API | HTTPS | X-Postmark-Server-Token (per-tenant; decrypted on demand) | Send email, configure webhook |
| Outbound | AWS Route53 | AWS SDK over HTTPS | STS-assumed role (auto-chained from pod IRSA role) | Tenant DNS record CRUD |
| Outbound | Aurora PostgreSQL | JDBC | DB credentials (existing operations component pattern) | email_configuration / email_job persistence |
| Inbound | Postmark webhook | HTTPS | Bearer token (ARDA_API_KEY); validated in-component by the receiving Ktor route | Delivery / Bounce / SpamComplaint events |
| Inbound | Consumer (SPA / BFF) | HTTPS | ARDA_API_KEY validated in-component (Application Runtime’s existing scheme) | Consumer-facing HTTP endpoints (email-configuration, email-job) |
8.4 AWS IAM identity chain
Section titled “8.4 AWS IAM identity chain”Route53 access traverses a role chain settled in DQ-204:
EKS Pod (IRSA service-account role) └─ has sts:AssumeRole on EmailDnsProvisioningRole └─ EmailDnsProvisioningRole (per partition) └─ Route53 ChangeResourceRecordSets on partition mail zoneThe AWS SDK’s StsAssumeRoleCredentialsProvider is configured at module startup with a 15-minute session duration. The provider auto-refreshes lazily; the L1 route53ZoneProxy makes Route53 calls without per-call AssumeRole.
8.5 Network boundaries
Section titled “8.5 Network boundaries”Inbound traffic transits API Gateway as a forwarding layer; authentication for the email service routes is performed in-component, in each route’s Ktor server configuration. The webhook route validates a Bearer token; consumer-facing routes use the Application Runtime’s existing in-component authentication scheme. API-gateway-level authorisers for the email-service routes are out of scope for this project; if and when the platform later adopts gateway-level authorisation as a cross-cutting concern, the email-service routes migrate alongside the rest. See cross-cutting-design.md § 2.2.
9. Functional → runtime → artifact mapping
Section titled “9. Functional → runtime → artifact mapping”| Functional element | Runtime resource | Source artifact |
|---|---|---|
| Postmark account references | Postmark-Prod and Postmark-NonProd items in Arda-SystemsOAM (global-utility, qualified names); Postmark items in per-partition Arda-{Env}OAM vaults (service-name-only, created during Phase 4) | src/main/cdk/platform/postmark-service.ts |
| 1Password vault references | Arda-SystemsOAM vault | src/main/cdk/platform/one-password.ts |
| GitHub Actions CI secret | OP_SERVICE_ACCOUNT_TOKEN repository secret in Arda-cards/infrastructure | provisioned via tools/gha-secret.ts (transition utility) |
ardamails.com root zone | Route53 hosted zone in platformRoot | Root stack at apps/Root/; declared in instances/Root/dns.ts |
arda.ardamails.com Corporate zone (incl. SPF and DMARC) | Route53 hosted zone in platformRoot (Corporate AWS-account-decoupled) | stacks/corporate/corporate-mail-dns.ts consuming constructs/xgress/dns-zone.ts |
freekanban.arda.ardamails.com records | Route53 TXT (DKIM) + CNAME (Return-Path) within arda.ardamails.com | stacks/corporate/free-kanban-tool-mail-dns.ts consuming constructs/xgress/dns-email-records.ts |
| Free Kanban Tool Postmark server | Postmark server instance in PostmarkProd account | platform/constructs/postmark/PostmarkServer (interim: created by Corporate CLI; target: Custom Resource) |
Per-partition mail zones (prod.ardamails.com, etc.) | Route53 hosted zones in partition AWS accounts | stacks/purpose/partition-email.ts consuming constructs/xgress/dns-zone.ts |
L4 endpoints (email-job, email-configuration, postmark-events) | API Gateway routes → ALB → operations pod | operations: shopaccess/email/endpoint/...; routes declared in operations CFN files |
| L3 services (EmailConfigurationService, EmailJobService) | EKS Deployment, EKS Pod (one of two replicas) | operations: shopaccess/email/service/... |
| L2 capability composers (TenantProvisioner, EmailSender) | Same pod, in-process Kotlin objects | operations: shopaccess/email/servers/capability/... |
| L1 protocol proxies (postmark{Account,Server}Proxy, route53ZoneProxy) | Same pod, in-process Kotlin objects | operations: shopaccess/email/servers/api/... |
| Encryption / KDF (TokenCipher, Hkdf) | Same pod, in-process Kotlin objects | operations: shopaccess/email/crypto/...; primitives in common-module |
| DNS verification bounded-polling tasks | Same pod, kotlinx coroutines on Application scope; activePolling map | in EmailConfigurationService |
| Persistence | Aurora RDS (Postgres), Database per Module pattern | Flyway migrations: shopaccess/email/database/migrations/ |
| HOCON configuration | application.conf + secrets.properties (mounted from K8s Secret) | operations: src/main/resources/shopaccess/email/application.conf + Helm |
| Postmark account-level secrets | AWS Secrets Manager → ESO → K8s Secret → pod | infrastructure repo (stacks/purpose/partition-email.ts); ESO entries in operations Helm chart |
| Per-tenant Postmark server tokens | DB column server_token_encrypted (AES-256-GCM versioned envelope, DQ-202) | email_configuration table |
| Per-partition encryption key | AWS Secrets Manager → ESO → HOCON (extras.email.encryptionKey) → HKDF in pod | infrastructure repo (partition-email stack) + L3 service |
IAM provisioning role (EmailDnsProvisioningRole) | AWS IAM, per-partition | infrastructure repo (partition-email stack) |
| Cross-cutting redact + structured logger | In-process TS code reused across CDK and tools | src/main/cdk/utils/logging.ts |
| 1Password SDK wrapper (CI + tools) | In-process TS code with dual-auth discovery | src/main/cdk/utils/one-password.ts |
| Drift-detection workflow (Corporate) | Scheduled GitHub Actions run; auto-issue on failure | .github/workflows/corporate-drift.yml (instance-group-scoped per DQ-R1-012) |
10. Decision traceability
Section titled “10. Decision traceability”Three decision logs cover the project after this revision:
- Upstream decisions (
decision-log.md,DQ-001throughDQ-013): mail root domain, tenant slug source, sending-domain shape, webhook authentication, server-token storage approach, IAM-role placement. - Application-layer decisions (
decision-log.md,DQ-201throughDQ-208): server module partitioning (L1 / L2 / L3), encryption algorithm + key derivation, STS strategy, persist-first lifecycle, slug derivation, trigger-driven DNS verification. - Revision-1 decisions (numbered
DQ-R1-NNN, to be added indecision-log.md): instance-group taxonomy,arda.ardamails.comtopology, Postmark-server creation interim/target mechanism, AWS-account-vs-instance-group decoupling, reserved-name discipline, Postmark thin-wrapper placement (platform/constructs/<provider>/), CDK code-hierarchy adoption.
Prior decisions invalidated by Revision 1 are marked as [superseded by DQ-R1-NNN] in the original log; new DQ-R1-NNN entries capture the replacement state.
The two current design documents — this overview and cross-cutting-design.md — cite the relevant DQs at each section. Phase-specific functional design and information model documents (planned for Phases 2–4) will follow the same convention.
11. References
Section titled “11. References”- Phase Structure — detailed per-phase scope, deliverables, and exit criteria.
- Runtime Overview (principles) — platform-wide IaC principles adopted by this project.
- Runtime documentation design review — proposed updates to runtime docs.
- Postmark-Foundations PR review — review of PR #445; actions tracked in
scratch.md. - Project goal.
- Cross-cutting design.
- Original architecture overview (superseded by this document).
Copyright: © Arda Systems 2025-2026, All rights reserved