Skip to content

Postmark API Observations

Design-intent note capturing observed Postmark API behaviour relevant to Arda. This is a curated reference — not an exhaustive API catalogue. For the full Postmark API surface, see the Postmark Developer Docs.

Items are annotated with their evidence source:

  • from-docs — pulled from Postmark’s published documentation.
  • observed — empirically confirmed against the live API during implementation or operator runs.

This note records the Postmark API surface assumptions Arda commits to during Phase 1 and beyond. It serves two audiences:

  • Operators provisioning or maintaining Arda’s Postmark accounts — consult the authentication and endpoint sections when using the Postmark API directly.
  • Engineers building the postmarkAccountProxy component in a later phase — use this note as the design starting point. Items marked observed are higher-confidence than from-docs items; both are useful, but observed entries should drive design decisions where they diverge.

The scope is limited to the API surface Arda actually touches: account-level administration (servers, domains, sender signatures) and outbound message submission. Inbound email, broadcast campaigns, and suppression management are out of scope.


Postmark uses two distinct token types with separate request headers:

Token typeHeaderScope
Account-level tokenX-Postmark-Account-Token: <token>Operations spanning servers: GET /servers, POST /servers, POST /domains, PUT /domains/<id>/verifyDkim, etc.
Server-level tokenX-Postmark-Server-Token: <token>Operations within a single server: POST /email, GET /messages/outbound, etc.

from-docs — header names are documented in the Postmark Developer Docs.

Phase 1 provisions Postmark accounts and stores account-level tokens in 1Password (Arda-SystemsOAM vault). These tokens authenticate all account-administration API calls made by the drift-detection workflow and, in a later phase, the postmarkAccountProxy.

Server-level tokens are generated at server-creation time and stored separately (one item per Postmark server). They are consumed at runtime by the email-sending component inside each partition, not by account-management code.

Postmark does not sign webhook payloads. Arda authenticates inbound webhook deliveries via an Authorization: Bearer <token> header configured on the webhook’s properties through Postmark’s modern Webhooks API (per DQ-011). The Bearer token is the existing ARDA_API_KEY value the Application Runtime already accepts; Arda’s webhook handler validates the header before processing any event. The token is stored in Secrets Manager and projected to pods via the External Secrets Operator (ESO) pattern — not via a GitHub Actions secret.

from-docs — see Postmark’s Webhooks API reference for the HttpHeaders property used to attach the Bearer token.


Arda operates two Postmark accounts:

  • PostmarkProd — bound to production partitions (prod, demo). Account-level credential stored in 1Password under Arda-SystemsOAM/Postmark-Prod (the global-utility item; see below for per-partition copies).
  • PostmarkNonProd — bound to development and staging partitions (dev, stage). Account-level credential stored in 1Password under Arda-SystemsOAM/Postmark-NonProd (global-utility item).

Account-level isolation provides a hard boundary: a token for one account cannot access servers, domains, or message logs of the other. This is the primary mechanism ensuring production email traffic is never affected by development activity.

Following the Arda partition-vault convention (Arda-{Dev,Stage,Demo,Prod}OAM), each partition also holds its own independently stored copy of the relevant Postmark account token under the service-name-only item title Postmark (the vault name carries the environment; the item title stays short):

PartitionVaultItemPostmark account
prodArda-ProdOAMPostmarkPostmarkProd
demoArda-DemoOAMPostmarkPostmarkProd
stageArda-StageOAMPostmarkPostmarkNonProd
devArda-DevOAMPostmarkPostmarkNonProd

These per-partition copies are stored independently from day one so any partition can rotate or diverge without infrastructure change. They are the runtime credential source for Phase 4’s per-partition deploy tooling (postmarkCredentialOpReference(partition) in platform/postmark-service.ts — a Phase 4 prerequisite). The Arda-SystemsOAM qualified items (Postmark-Prod, Postmark-NonProd) remain for cross-partition utility use (CI drift-detection, operator tooling that spans partitions) because Arda-SystemsOAM holds both accounts and needs the qualified names for disambiguation.

A Postmark server is the unit of sender identity inside an account. Each server has:

  • A unique numeric ID and a display Name.
  • One or more ApiTokens (server-level). Postmark supports multiple tokens per server to enable zero-downtime rotation; Arda currently provisions one token per server.
  • A DeliveryType of Live (production sending) or Sandbox (no real delivery). Integration test runs use Sandbox.

DeliveryType is the signal for live-sending readiness. Postmark’s API has no account-level sandbox endpoint: GET / 302-redirects to the marketing site, and GET /account returns HTTP 404. The correct way to determine whether a server will actually deliver mail is to read the DeliveryType field returned by GET /servers/{id} after server creation. The Phase 3 Corporate CLI checks DeliveryType after creating the Free Kanban Tool server (Phase A, step after server creation) and emits a structured log event: info level for Live, warn for Sandbox or unknown. REQ-OPS-002’s operator-confirmation intent is preserved; the implementation uses the per-server endpoint rather than a non-existent account endpoint.

A Postmark domain belongs to the account, not to a specific server. Multiple servers in the same account can send From addresses on the same domain. Arda creates both the server and the domain within the same account; no explicit server-to-domain association is required.

from-docs (server and domain structure). observed (no account-level sandbox endpoint; DeliveryType from GET /servers/{id} is the live-delivery signal).


These are the endpoints Arda’s automation touches. Refer to the Postmark Developer Docs for full request and response schemas.

MethodPathPurpose
GET/serversList all servers in the account. Used for idempotent pre-flight check before creating a server. Both count and offset query parameters are required — omitting either returns HTTP 422 with ErrorCode: 600, "Parameter '<name>' is required but has been left out". The minimal probing request is GET /servers?count=1&offset=0.
GET/servers/<id>Retrieve a single server by numeric ID.
POST/serversCreate a new server. Returns the server object including ApiTokens.
MethodPathPurpose
GET/domainsList all domains in the account. Used for idempotent pre-flight check.
POST/domainsCreate a new sending domain. Returns DKIMHost and DKIMTextValue (the DNS record to publish).
PUT/domains/<id>/verifyDkimRe-check DNS and update DKIMVerified. Idempotent; safe to poll.
PUT/domains/<id>/verifyReturnPathRe-check the Return-Path CNAME and update ReturnPathDomainVerified. Idempotent; safe to poll.

Key Domain response fields Arda reads:

  • DKIMHost — FQDN for the DKIM TXT record (e.g., pm._domainkey.sending.example.com). The DKIM selector is derived by stripping ._domainkey.<domain>.
  • DKIMTextValue — the TXT record body. Public DNS material (it is published in DNS for receivers to fetch). Captured into cdk.context.json by the Phase 3 Corporate CLI’s Phase A so the CDK Stack in Phase B can emit the DNS record. Not a secret. The Postmark server token returned alongside this response is the secret; it is written directly to 1Password (e.g., Free-Kanban-Generator-Postmark-Server in the Arda-CorporateOAM vault per DQ-R1-007) and never traverses CDK context.
  • ReturnPathDomain / ReturnPathDomainCNAMEValue — FQDN and CNAME target for the Return-Path record. Public DNS material, same disposition as DKIM above.

Sender signatures are distinct from domains: they authorize specific From addresses (individual mailboxes) rather than entire domains. Arda uses domain-level verification as the primary sending authorization; sender signatures are used only where a specific named mailbox (no-reply@…) requires explicit verification.

MethodPathPurpose
GET/sendersList sender signatures. Pre-flight check.
POST/sendersCreate a sender signature for a specific From address.
MethodPathPurpose
POST/emailSend a single transactional message. Requires a server-level token.
POST/email/batchSend up to 500 messages in one request.
GET/messages/outboundQuery sent message history (search by recipient, tag, status).

Postmark does not guarantee idempotent behavior on POST resource-creation endpoints. Arda’s strategy is client-side pre-flight checking:

  1. Issue a GET list call before any POST create call.
  2. If a resource with the matching name already exists, skip the create and reuse the existing ID.
  3. Log a warning noting the resource was found pre-existing.

This pattern applies to server creation (POST /servers) and domain creation (POST /domains). The verification endpoints (verifyDkim, verifyReturnPath) are inherently idempotent on Postmark’s side — each call re-checks DNS and updates the verification flag. Arda polls them (default 5 attempts, 60 seconds apart) until both verifications succeed or the budget is exhausted.

from-docs for the documented behavior. observed annotations to be added when the drift-detection workflow runs against PostmarkProd.

Postmark expresses errors as a JSON body alongside a standard HTTP status code:

{
"ErrorCode": 504,
"Message": "Domain name 'sending.example.com' is invalid"
}

Arda’s client preserves both fields in a structured error type. The retry policy by HTTP status:

HTTP statusRetryable?Handling
5xxYesBounded exponential backoff — 3 attempts by default, doubling the delay between each.
422NoBusiness error (invalid name, duplicate, missing field). Surface ErrorCode + Message to the caller; do not retry.
401 / 403NoAuthentication failure — almost always a wrong token. Check 1Password for the correct account credential.
429DeferredRate limit. Not observed in practice given Arda’s low request volume (~10 calls per orchestration run). If encountered, add observed header details here.

from-docs. Unit tests cover the 5xx-retry-success, 5xx-budget-exhausted, 4xx-fail-fast, and malformed-JSON paths.

Postmark documents X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset response headers but does not publish a specific budget for account-level operations on the Platform plan. Phase 1’s request volume is negligible. If later phases introduce higher-volume account-level traffic (e.g., bulk domain listing), capture header observations here.

from-docs.


FieldValue
Postmark API base URLhttps://api.postmarkapp.com
Postmark planPlatform
Observation note authored2026-04-28
Freshness recommendationRe-validate on the first drift-test failure attributable to surface drift, with an annual review as a backstop (DQ-R1-005).

The platform/postmark-service.ts file in the infrastructure repository holds a structured constant (POSTMARK_API_VERSION_PIN) that records this note’s URL and the freshness date, making the version-pin machine-discoverable without duplicating content.


  • Postmark Service Overview — account topology, credential storage, and OAM model.
  • Operator Runbook — step-by-step provisioning instructions and troubleshooting guide.
  • OAM Overview — FCAPS context for the Postmark service within the broader system management model.