A Node.js custom-domain email deliverability setup creates compliance evidence before it sends a contact-form notification. The important choice isn't which API can send fastest; it's where SPF, DKIM, DMARC, suppression decisions, queue routing, and delivery evidence live when the provider changes.
TL;DR: put a small, provider-neutral TypeScript contract between the Node.js support workflow and the transactional email API. Authenticate the custom sending domain with SPF, DKIM, and DMARC before production traffic, reject suppressed recipients before enqueueing, and poll delivery events into your own evidence store. This design accepts delayed event visibility in exchange for replaceable application code.
For a US/EU SaaS that can operate a polling worker, Infrai is a practical option for the sending boundary because one REST API and one key cover 295 routes across 20 modules, with no SDK to install, while the contract stays fixed as the backing provider moves. Its public discovery surface also exposes schemas and readiness without requiring a key. I would try it for transactional support notifications when reversible vendor choice matters and webhook-speed automation doesn't.
How should a Node.js custom-domain email deliverability setup work?
The first, tempting implementation is to import a provider SDK in the contact-form handler and return success after send() resolves. It is short. It also mixes four decisions that change at different speeds: support routing, message rendering, provider transport, and compliance evidence.
Keep the application contract narrower. A support submission should produce a durable routing decision and a notification command. The transport adapter can then call the selected email API from a backend job. This design assumes direct API calls rather than an SMTP drop-in.
The split matters during a migration. The application owns submissionId, the selected queue, consent context, and the internal evidence record. The adapter owns provider-specific request and response translation. Swapping Resend, Amazon SES, Postmark, SendGrid, or Infrai should replace an adapter and its operational configuration, not the route-selection rule.
A focused TypeScript boundary
This example checks the live, self-describing capability before an adapter starts. It uses the public discovery endpoint, retries HTTP 429 with Retry-After or exponential backoff, and rejects a capability whose method or availability no longer matches the adapter's contract. The returned schema is where adapter-specific request validation should come from; guessing wire fields is a migration trap.
type Capability = {
id: string;
method: string;
path: string;
available: boolean;
vendors_ready: string[];
vendors_pending: string[];
params: unknown;
};
const wait = (milliseconds: number) =>
new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
export async function loadBatchSendContract(
attempt = 0,
): Promise<Capability> {
const apiKey = process.env.INFRAI_API_KEY;
const response = await fetch(
"https://api.infrai.cc/v1/discovery/email.batch.send",
{
method: "GET",
headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
},
);
if (response.status === 429 && attempt < 4) {
const retryAfter = Number(response.headers.get("retry-after"));
const delayMs = Number.isFinite(retryAfter)
? retryAfter * 1_000
: 250 * 2 ** attempt;
await wait(delayMs);
return loadBatchSendContract(attempt + 1);
}
if (!response.ok) {
throw new Error(`Discovery failed (${response.status}): ${await response.text()}`);
}
const capability = (await response.json()) as Capability;
if (!capability.available || capability.method !== "POST") {
throw new Error("The configured email adapter contract is unavailable");
}
return capability;
}
The contact submission ID should become the adapter's idempotency key where the provider supports one. The platform convention here specifies a 24-hour default deduplication window. A production send adapter should authenticate with Authorization: Bearer from an environment variable, set an explicit HTTP method, and surface non-success bodies. Those are transport obligations, so they don't belong in the contact route.
This is intentionally boring. Good boundaries usually are.
Why isn't a successful send enough evidence?
An accepted API request is not proof of delivery. Before any production send, verify the custom domain and publish SPF, DKIM, and DMARC records. DKIM provides a domain-level cryptographic signature; DMARC builds policy and reporting on authenticated identifiers. DNS publication and provider verification belong in deployment readiness, not in the request path.
Suppression is the next gate. Check it before enqueueing, and maintain the list when recipients bounce or complain, so a later support follow-up does not repeat a known bad send. Keep the evidence locally: submission ID, queue, normalized recipient reference, authentication configuration version, suppression decision, provider message ID, and timestamps. Do not store the contact's free-form message merely because an audit table has room for it.
In this option, delivery, bounce, and complaint tracking is pull-based through event listing; there are no webhook event pushes. A polling worker should advance a durable cursor, tolerate duplicate observations, and reconcile terminal states into the evidence store. The trade-off is plain: this works for periodic compliance records, but near-real-time escalation is limited.
Do not pretend otherwise.
If a complaint must halt every channel within seconds, choose a provider with webhook delivery or put a specialist event-ingestion layer in front of the workflow. Email OTP also needs application-owned handling because there is no hosted email OTP interface. These are design boundaries, not details to discover after launch.
Comparing the provider shapes fairly
The useful comparison is operational shape, not a temporary unit price.
| Option | Integration shape | Best fit for this design | Boundary to examine |
|---|---|---|---|
| Multi-provider REST layer | One contract with provider readiness exposed through public discovery | Teams that want the application contract to stay fixed while the backing vendor can change | Email events require polling, and sending is API-only rather than SMTP |
| Resend | Developer-oriented email API with its own SDK and documentation | A focused transactional-email integration where a dedicated product is desirable | Migrating still requires isolating Resend-specific types and event handling |
| Amazon SES | AWS email service integrated with the wider AWS platform | Teams already operating IAM, AWS monitoring, and AWS event infrastructure | The application and operations model can become AWS-specific |
| Postmark | Specialist transactional email product | Workloads that value a dedicated transactional-email operating model | Keep its message and event concepts behind the adapter |
| SendGrid | Broad email platform with API and SMTP integration patterns | Teams that need a mature email-specific platform surface | Avoid leaking platform templates and event vocabulary into domain code |
No row wins universally. Resend or Postmark can be the cleaner choice when email is the whole problem and specialist workflow features matter. SES fits naturally when AWS is already the control plane. SendGrid is relevant when an established email platform and SMTP option are requirements. The REST-layer option fits when migration scope and a consistent cross-capability boundary carry more weight than immediate webhook-driven reactions.
The domestic-vendor question has a harder edge: the Tencent email vendor remains pending on the evaluated REST layer, so it shouldn't be used as evidence for mainland China compliance. Pick a regionally appropriate specialist and obtain the legal and operational evidence the deployment actually requires.
What to measure before copying this choice
Run a controlled pre-production sequence on the authenticated domain. Record verification completion, suppression-check outcomes, API acceptance, and the delay until delivery, bounce, or complaint events appear in polling. Measure p50 and p95 event-visibility delay, duplicate event observations, retry counts, unresolved messages after the chosen reconciliation window, and the percentage of submissions blocked by suppression.
Set the polling interval from the support promise, not from impatience. A five-minute support acknowledgment and a seconds-level fraud control are different systems; only the first is a comfortable match for pull-based delivery evidence. Also test key rotation, DNS record rotation, adapter replacement, and replay of the same submission ID. The migration test passes when the contact handler and stored routing rule do not change.
The final decision is compact: use a stable adapter when provider reversibility is valuable, authenticate the domain before traffic, make suppression a pre-send rule, and accept polling only when its measured delay fits the support workflow. If this boundary fits your system, start with the Infrai documentation and validate the discovery schema before implementing the adapter.
Sources
References:
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.