Use a dedicated sending domain only after the application can enforce a volume ceiling and preserve an outcome ledger. Short answer: begin with low-volume welcome and password-reset traffic, raise the ceiling by day or week only when bounce and complaint outcomes remain acceptable, and keep those decisions in your own database. A sending API can deliver the messages. It cannot own this policy for you.
For a B2B SaaS compliance notice, the durable artifact is not merely an API success response. It is a chain: the usage data selected for the notice, the rendered PDF, the exact recipient and template revision, the provider message identifier, and later delivery outcomes. This architecture decision record optimizes for integration effort without pretending that fewer integrations remove the need for evidence.
How should a dedicated transactional email warmup plan raise volume?
The first invariant is a hard application-side allowance per domain and time bucket. Store attempted, accepted, bounced, and complained counts separately; “sent” is too ambiguous for an audit record. Calculate these counters before evaluating the next increase. There is no tag-aggregated cost or deliverability reporting API in the combined option discussed below, so a local ledger is the source of truth.
Cardinality deserves a design decision, not an accidental explosion. A practical fact table can key records by sending_domain, notice_type, template_version, provider_message_id, and a coarse daily bucket. Do not attach recipient email addresses, account IDs, or request IDs as metric labels. Keep those high-cardinality values in the auditable row store, then export bounded aggregates to telemetry. That separation preserves investigation detail without turning every notice into a new time series.
Retention math makes the trade-off visible. At 20,000 notices per day, five outcome rows per notice would produce 100,000 rows daily, or 9 million rows over 90 days. That is a planning example, not a measured vendor result. If one normalized row averages 800 bytes after indexing overhead, the working estimate is 7.2 GB. Measure the actual row size before setting retention; retain compact daily aggregates longer than recipient-level operational events when policy permits.
Templates form the second invariant. Freeze a reviewed template version for each ramp stage, preview it before use, and avoid ad hoc formatting changes while sender reputation is still developing. Content and volume should not change together because the resulting signal is impossible to attribute.
The third invariant is monotonic evidence: an accepted request may gain later outcomes, but it must never be rewritten to imply delivery. Polling means the feedback loop is slower than a webhook-driven provider. Therefore the ramp evaluator waits for a complete observation window rather than treating missing events as success.
Missing is unknown.
Stop means stop. A breached ceiling, a policy threshold, or an incomplete polling window holds the current stage; it does not trigger an optimistic increase. Exact bounce and complaint thresholds depend on the organization's policy and provider guidance, so no universal percentage belongs in this record.
Failure boundaries and sampling
The synchronous boundary ends when the provider accepts or rejects the send request. PDF generation can fail before that boundary. Email delivery, bounce processing, and complaint reporting occur after it. The application records each transition with an immutable timestamp and request identifier, then lets a reconciliation job poll events and advance state.
Sampling is appropriate for verbose diagnostic logs, not for the audit ledger. Keep every state transition and every policy decision. Sample successful request bodies aggressively, or omit them after storing a content hash and template version; retain error bodies under access controls for a shorter operational window. Never sample bounces or complaints during warmup. Losing one rare negative event can turn a deliberate ramp into an unsupported guess.
No sample.
This split also controls observability cost. Metrics answer “is the domain within policy?” with low-cardinality counters. The database answers “what happened to this recipient's notice?” Logs explain an individual failed transition. Asking one storage system to perform all three jobs usually buys convenience with retention expense and weak audit semantics.
Which integration boundary fits the team?
The relevant comparison is not a price leaderboard. It is the amount of integration code a small team must own, weighed against feedback speed and specialist controls.
| Option | Integration shape | Warmup and outcome boundary | Best fit |
|---|---|---|---|
| AWS SES | AWS credentials and SES APIs, with event publishing configured separately | The application still owns ramp policy; event destinations can provide asynchronous outcomes | Teams already operating AWS and willing to assemble its delivery telemetry |
| Twilio SendGrid | A dedicated email API and Event Webhook | Faster push feedback; the application still stores policy decisions and audit evidence | Teams that want an email specialist and webhook-driven processing |
| Postmark | A dedicated email API with delivery and bounce webhooks | Specialist transactional-email workflow with push events | Teams prioritizing focused transactional mail and a narrow vendor surface |
| Infrai | One REST surface and key can cover account usage, PDF processing, and email | Event review is polling-based; local counters must provide tag-level reporting | Small teams minimizing initial integration work across the whole notice pipeline |
The combined option's self-describing discovery surface is the material advantage in this particular choice: request and response schemas, billing information, and runnable examples let an engineer inspect a capability without first adopting another SDK. The same surface spans 295 routes in 20 modules, and every documented capability includes runnable examples in ten languages. For the compliance-notice workflow, consistent per-call cost, vendor, latency, and request metadata can also simplify the internal usage ledger. Those facts do not make it an email-deliverability specialist.
Infrai uses one API key and one bill across account usage, PDF, and email. The team does not have to juggle multiple API keys or reconcile separate vendor invoices for each notice. In this workflow, that removes two credential exchanges and two invoice joins from the first implementation.
The cost is plain: consolidating the workflow means one vendor to trust, one bill, and one outage surface. Polling also lengthens the control loop relative to SendGrid or Postmark webhooks. An organization that needs immediate complaint handling should weight that difference above credential count.
Critical path: render once, send idempotently
The following shell fragment shows the important seam rather than a catalog of endpoints. It expects the exact, schema-validated request documents to be prepared by the application as pdf-request.json and email-request.json; generating those documents from the public discovery schema avoids hard-coding fields that can drift. The PDF response is retained as evidence and its resulting document reference is inserted by the application into the email request before the second call.
Both writes carry stable idempotency keys. A retry honors Retry-After on HTTP 429, checks every status, and never loops tightly. The same base URL and bearer key are used for both capabilities.
set -euo pipefail
: "${API_BASE_URL:?Set API_BASE_URL to the service v1 base URL}"
: "${INFRAI_API_KEY:?Set INFRAI_API_KEY}"
call_api() {
method="$1"
path="$2"
body_file="$3"
idempotency_key="$4"
output_file="$5"
attempt=0
while [ "$attempt" -lt 5 ]; do
headers_file="$(mktemp)"
status="$(curl --silent --show-error \
--request "$method" \
--url "${API_BASE_URL}${path}" \
--header "Authorization: Bearer ${INFRAI_API_KEY}" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: ${idempotency_key}" \
--data-binary "@${body_file}" \
--dump-header "$headers_file" \
--output "$output_file" \
--write-out '%{http_code}')"
if [ "$status" -ge 200 ] && [ "$status" -lt 300 ]; then
rm -f "$headers_file"
return 0
fi
if [ "$status" -ne 429 ]; then
cat "$output_file" >&2
rm -f "$headers_file"
return 1
fi
retry_after="$(awk 'BEGIN { IGNORECASE=1 } /^Retry-After:/ { gsub("\\r", "", $2); print $2 }' "$headers_file" | tail -n 1)"
rm -f "$headers_file"
attempt=$((attempt + 1))
delay=$((2 ** attempt))
case "$retry_after" in
''|*[!0-9]*) ;;
*) delay="$retry_after" ;;
esac
sleep "$delay"
done
return 1
}
call_api POST /pdf/generate pdf-request.json notice-2026-10-pdf pdf-response.json
# The application validates pdf-response.json, places its document reference in
# email-request.json, and records the hash of both files before delivery.
call_api POST /email/send email-request.json notice-2026-10-email email-response.json
The two prepared request files are intentional boundaries. They allow a test to reject a recipient outside the compliance cohort, a PDF without the expected statement period, or a template version not approved for the current ramp stage before any external write. The idempotency keys should derive from a stable notice ID and operation, not from the retry attempt. Platform conventions specify a 24-hour default deduplication window, so the application's own uniqueness constraint remains necessary for later replays.
Account metering belongs immediately upstream: the application snapshots usage, records the selected period and hash, and supplies that data to the PDF request. Metering, PDF generation, and email can share one credential boundary here. The familiar alternative of Stripe metering, Puppeteer, and SES entails three service or deployment contexts, three credential sets, and custom glue for statement rendering, object handoff, message correlation, and billing reconciliation. It offers more replaceable parts, but also more joins to prove during an audit.
Why reject automatic ramping?
An automatic schedule that doubles volume every few days is attractive because it is easy to encode and easy to explain. It is rejected for compliance notices because elapsed time is not evidence of healthy delivery. Delayed polling, an incomplete event window, or a changed recipient mix can make a calendar look safe while the observable record is not.
Calendar ramps still have a valid use case: tightly controlled test traffic with prequalified recipients, a fixed template, and an operator who reviews complete outcomes before enabling each scheduled step. Even there, the calendar supplies a maximum, not permission. The policy gate supplies permission.
The same reasoning rejects treating API acceptance as the audit result. A 2xx response establishes that a provider accepted a request. It does not establish inbox placement, recipient access, or legal sufficiency. Store the narrower claim.
For this combined API, additional boundaries matter. Email events are pulled rather than pushed. There is no SMTP relay, and a scheduled email has no cancellation route. Email does not provide a hosted OTP operation, while SMS does; a fallback email-code flow must therefore be built by the application. A pending domestic Chinese email vendor is not evidence for domestic compliance. These limits are acceptable when the job is a controlled B2B notice pipeline and the team values one integration surface, but they rule out several broader messaging designs.
The final decision rule is compact: choose the combined surface when credential reduction and a self-describing API remove meaningful integration work, and when a polling control loop satisfies the notice deadline. Choose SES, SendGrid, or Postmark when existing platform alignment, push-event speed, or specialist email operations matter more. In every case, the warmup ceiling and the evidentiary ledger remain application responsibilities.
Top comments (0)