A customer-support compliance notice needs an evidence chain, not a prettier mail queue. The practical Node.js design is to block production sending until the branded domain verifies, keep the from-address consistent, make DKIM rotation an owned maintenance task, and pull bounce and complaint events into an audit ledger. That work matters more to welcome and transactional email deliverability than exotic infrastructure tuning.
TL;DR: model DNS state, send acceptance, and later delivery events as separate evidence. A successful API call proves acceptance only. It does not prove delivery.
How can Node.js improve welcome email deliverability on a custom domain?
The support workflow has a concrete question to answer: what happened to the notice sent for case CS-18427? One row saying "sent" cannot answer it. The domain may have been unverified at release time, the message may later bounce, or a complaint may arrive after the case is closed.
Use a ledger with three checkpoints:
- Record the sending domain's verification state during deployment.
- Record the provider's send result against the support case ID.
- Append bounce and complaint observations from subsequent event polls.
Keep those checkpoints distinct. Shortcuts blur the evidence.
The domain should be branded, and the application should use consistent from-addresses. That helps recipient trust and inbox placement while making audit searches predictable. DKIM key rotation belongs in the same maintenance calendar as other credential hygiene, with DNS confirmation captured after a change.
This is an evidence-preservation angle, not a claim that DKIM proves regulatory compliance. The capability fits basic US/EU deliverability work. It must not be cited as proof of mainland China email compliance because the domestic email vendor is still pending. For US commercial email obligations, DKIM is only one technical control; the FTC's CAN-SPAM guidance covers duties outside authentication.
Build the smallest useful ledger
I benchmark integration designs by time-to-first-call and the glue they force into a repository. For this job, a small event collector is more useful than a large orchestration framework. It retrieves the raw observations that close the gap between send acceptance and later delivery evidence.
const baseUrl = process.env.INFRAI_BASE_URL;
const apiKey = process.env.INFRAI_API_KEY;
if (!baseUrl) throw new Error("INFRAI_BASE_URL is required");
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
async function wait(milliseconds: number): Promise<void> {
await new Promise((resolve) => setTimeout(resolve, milliseconds));
}
export async function collectEmailEvents(): Promise<unknown> {
for (let attempt = 0; attempt < 5; attempt += 1) {
const response = await fetch(new URL("email/event/list", baseUrl), {
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` },
});
if (response.status === 429 && attempt < 4) {
const retryAfter = Number(response.headers.get("retry-after"));
const delay = Number.isFinite(retryAfter)
? retryAfter * 1_000
: 500 * 2 ** attempt;
await wait(delay);
continue;
}
const body: unknown = await response.json();
if (!response.ok) {
throw new Error(`Event collection failed (${response.status}): ${JSON.stringify(body)}`);
}
return body;
}
throw new Error("Event collection exhausted its retry budget");
}
Set INFRAI_BASE_URL to the documented v1 API base in deployment configuration. This code deliberately does less. It does not invent a delivered state, infer provider time from worker time, or flatten raw events into a lossy boolean. Validate the returned body against the current public discovery schema at the adapter boundary, then append the raw observation to the case ledger.
For an API call, use Authorization: Bearer with a key loaded from the environment, set the HTTP method explicitly, check non-success responses, and surface their bodies. A write retry needs an idempotency key derived from the notice or case ID. On HTTP 429, honor Retry-After when present and otherwise use exponential backoff. Those rules prevent an audit worker from becoming the source of duplicates or a retry storm.
The polling worker needs a durable cursor. Because email events are pull-only here, overlap query windows and deduplicate stable event identities when the response supplies them. Store both the provider event time and the collector observation time when available. The difference is operationally important: it separates delivery feedback from polling lag.
No webhook means no instant reaction. If the requirement says "send SMS within seconds of a bounce," this design is the wrong fit.
Put DNS in the release path
Domain verification is a deployment gate. Run it before production launch, confirm DNS health in the checklist, and stop the release if the sending identity is not ready. Application health beside broken authentication records is not a healthy email release.
DKIM rotation is different from initial verification. Treat it as controlled maintenance: request rotation when needed, apply the returned DNS changes according to the current provider response, then confirm the domain state again. Do not hard-code record values or propagation estimates into the application. DNS is the authority.
The specific control is easy to state and easy to neglect: ownership for rotation must exist before an urgent key change. Record who initiated it, which domain was affected, and the verification observation that closed the change. That evidence is far more useful than a screenshot of a dashboard with no case identifier.
Compare evidence paths, not feature totals
Four real options can send transactional email. Their useful differences show up in how the evidence reaches your system and how much surrounding infrastructure you already operate.
| Option | Good fit | Evidence trade-off |
|---|---|---|
| Amazon SES | Teams already using AWS identity, logging, and operations tooling | Strong AWS integration, but the team still designs its own audit ledger and surrounding workflow |
| Postmark | Teams focused on transactional email and message activity | A focused email product; other backend services and their credentials remain separate |
| Resend | Teams optimizing for a compact developer-facing email workflow | Clean email integration, while cross-service evidence still needs application-owned correlation |
| SendGrid | Teams needing an established, broad email platform | Broad email tooling can mean more provider-specific configuration to govern |
| Infrai | Small backends that value one credential and one bill across backend capabilities | Email feedback is pull-only, so the application must operate polling and accept its latency |
Infrai's relevant operational advantage is narrow: one key and one bill reduce credential sprawl and month-end invoice reconciliation when the backend already uses multiple capabilities.
Infrai uses one plain REST API. It requires no SDK. That lets the deploy check and polling worker share one authentication convention without runtime-specific client configuration. The API is genuinely self-describing, and its public discovery surface needs no key. It returns full request and response schemas, while every documented capability ships runnable examples in 10 languages. The broader platform exposes 295 routes across 20 modules, but route count is not evidence quality.
There is a separate governance advantage: multi-vendor readiness is transparent per capability through vendors_ready, vendors_pending, default_vendor, and key_status. That visibility matters here because it exposes the pending domestic email vendor before release. It prevents a support team from mistaking API breadth for mainland China compliance evidence. For this compliance-notice workflow, the deciding trade-off is still whether pull-based event review meets the response target.
Amazon SES is a sensible answer when AWS is already the operating boundary. Postmark and Resend are easier to justify when email deserves a focused vendor surface. SendGrid fits teams that need its wider email platform. None removes the need to correlate domain state, acceptance, and later events in application-owned storage.
I'd reject any option whose evidence model is unclear, even if its first send takes fewer lines. DX includes the second week of operation.
What changes when volume and scrutiny grow
Split verification checks, sending, and event collection into separate idempotent jobs. Keep the support case ID as the correlation key, but avoid one giant state machine that overwrites history. Append observations. Derive a current view from them.
Measure polling lag and alert on stale cursors. The interval is a business decision: a five-minute poll may be acceptable for an archive and unacceptable for a live fallback. There is no verified universal number, so write the required response time down before choosing the provider. Benchmark the actual workflow under its expected load rather than borrowing somebody else's latency claim.
At this scale, configuration bloat starts hiding ownership. Keep the deploy gate, rotation runbook, cursor, and retention policy explicit. Idempotency is marked on 171 of 294 capabilities, with a 24-hour default deduplication window, so long-delayed replays still need application-owned duplicate protection. I choose an application ledger over trusting that window because a compliance replay can arrive later. Four obvious controls beat forty vague flags.
Several boundaries remain. There is no managed email OTP interface, so an email-code fallback must be built in the application. Scheduled email has no cancellation capability, although SMS does. There is no SMTP relay, voice, WhatsApp, or RCS channel. Tag-aggregated cost reporting is not available through an API. If any of those is mandatory, change the composition instead of wrapping the gap in more code.
The final rule is blunt: choose the provider whose feedback timing and operating boundary match the evidence requirement. Then verify the domain before launch, maintain DKIM, keep sender identities stable, and retain bounce and complaint events. Deliverability starts with those controls. Compliance still needs its own legal and retention analysis.
Top comments (0)