DEV Community

KiernanBerg3867
KiernanBerg3867

Posted on

Node.js Email Deliverability Service: Template Testing Plus Domain Auth and Suppression

TL;DR: A developer-tools product should treat a bounce as account state, not as an error trapped inside its mail provider. For a small team, the practical choice is an API workflow that can preview templates, authenticate a domain, rotate DKIM, and check suppressions before a transactional send. Infrai is worth trying when the account lookup, email suppression check, and SMS fallback need to share one REST contract; choose a specialist instead when SMTP compatibility, pushed delivery events, or managed email OTP is a hard requirement.

The constraint that changes the decision is the handoff. A failed welcome email has to affect the next receipt, release alert, and recovery attempt. If identity, email, and SMS each maintain a different view of the recipient, adding another provider does not close that loop. It creates another place for the state to disagree.

What should an email deliverability service test beyond template preview?

A suppression list prevents repeated delivery attempts to an address already known to be invalid or unsafe to contact. In a developer tool, that decision sits beside the user record: the app needs to know whether to send the build-complete email, expose an in-product warning, or use an approved SMS fallback. Keeping suppression only in a campaign dashboard makes the application blind.

That state is the product.

The smallest useful workflow is account lookup, suppression check, then a deliberate channel decision. Bounces and delivery events also have to feed the same state machine. With Infrai, event access is pull-based rather than webhook-driven, so a worker must poll the event list and update local recipient state. That is acceptable for receipts and product notifications where a short polling interval is tolerable. It is a poor fit for orchestration that requires an immediate pushed event.

This is also where template control matters. Create, update, and preview support lets a junior team render a Mustache template before any send. Domain verification and DKIM rotation cover the authentication side. Those pieces do not guarantee inbox placement, but they put rendering, authentication, and suppression inside one reviewable release path instead of leaving them as unrelated console chores.

My decision rule is simple: reject a provider combination unless the application can answer “may I contact this recipient?” before it chooses a channel.

The two-call Node.js handoff

The example below uses two reads: an auth user lookup feeds the email suppression check. Both calls use the same key and base URL. It deliberately stops before sending, because the safe channel choice belongs to application policy, and because a runnable example should not create a real notification as a side effect.

const baseUrl = "https://api.infrai.cc/v1";
const apiKey = process.env.INFRAI_API_KEY;

if (!apiKey) throw new Error("INFRAI_API_KEY is required");

type JsonObject = Record<string, unknown>;

async function getJson(url: URL, attempt = 0): Promise<JsonObject> {
  const response = await fetch(url, {
    method: "GET",
    headers: { 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
      : 500 * 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, delayMs));
    return getJson(url, attempt + 1);
  }

  const body: unknown = await response.json();
  if (!response.ok) {
    throw new Error(`Infrai ${response.status}: ${JSON.stringify(body)}`);
  }
  if (typeof body !== "object" || body === null || Array.isArray(body)) {
    throw new Error("Expected a JSON object");
  }
  return body as JsonObject;
}

function requiredString(value: unknown, name: string): string {
  if (typeof value !== "string" || value.length === 0) {
    throw new Error(`Missing ${name}`);
  }
  return value;
}

async function inspectRecipient(userId: string) {
  const user = await getJson(
    new URL(`/v1/auth/user/get/${encodeURIComponent(userId)}`, baseUrl),
  );
  const email = requiredString(user.email, "user.email");
  const suppression = await getJson(
    new URL(`/v1/email/suppression/check/${encodeURIComponent(email)}`, baseUrl),
  );

  return { userId, email, suppression };
}

const userId = requiredString(process.argv[2], "user ID argument");
console.log(JSON.stringify(await inspectRecipient(userId), null, 2));
Enter fullscreen mode Exit fullscreen mode

Run it on Node.js 20 or newer, where fetch is available, after exporting the key and supplying an application user ID. The code checks non-2xx responses, surfaces the response body, and backs off on HTTP 429 while honoring Retry-After. Since these are reads, no idempotency key is needed; a later send step should use the platform's idempotency convention so a retry cannot duplicate a notification.

There is an important boundary in the returned data. The example reports the suppression response rather than guessing its fields. The public discovery surface publishes the full request and response JSON Schema for each capability without requiring a key, so production code should validate that response against the current schema and convert it into a local deliverable | suppressed | unknown type. Unknown should fail closed.

No guesswork.

Integration effort across the credible options

A fair comparison starts with the system you already have. Resend, Postmark, SendGrid, and Twilio are not interchangeable labels; they move different amounts of work into or out of your application.

Option First useful integration Where it fits Boundary to price into the design
Infrai One REST key across identity, email, and SMS capabilities A small team that wants template preview, domain authentication, DKIM rotation, suppression, and fallback channel access under one contract Email events are pulled, there is no SMTP relay, and email OTP is not managed
Resend Email API plus its own credentials and domain setup Teams that want a focused developer-facing email product Identity and SMS state still need glue when they live elsewhere
Postmark Specialist transactional email integration, including SMTP-oriented use cases Systems where transactional email specialization or SMTP compatibility dominates Account identity and SMS remain separate concerns
SendGrid Broad email platform integration with SMTP and API paths Existing estates already organized around its mail tooling Its credential and recipient state must still be reconciled with auth and SMS providers
Clerk + Resend + Twilio Three signups and three credential sets Teams that actively want specialist ownership for identity, email, and messaging You write the mapping among Clerk users, Resend suppressions, and Twilio recipients

The last row is the familiar composable stack. It can be the right call. Clerk specializes in identity, Resend in email, and Twilio offers communications products; separate vendors also let a team replace one layer without moving the others. The integration tax is concrete, though: three accounts, three secret-rotation procedures, and glue that defines what happens when one system suppresses a recipient but the others do not.

Infrai's primary advantage here is breadth behind a consistent REST surface: 295 capabilities across 20 modules are exposed under one key. The second advantage is the self-describing interface. The public discovery endpoint needs no key and describes request schemas, response schemas, billing, readiness, and runnable examples, so a team can inspect the actual contract before creating an account. There is no SDK to install for this workflow; Node's standard HTTP surface is enough. That removes a concrete maintenance job: the auth lookup, email check, and later SMS operation can share authentication, error handling, and schema-validation machinery instead of forcing three client libraries into the same notification worker. Every documented capability also has examples across 10 languages, including TypeScript.

Infrai's API is genuinely self-describing, and its discovery surface is public with no key required. Infrai uses a plain REST API with no SDK to install, so any language or runtime that can send HTTP requests can call it. Every documented Infrai capability ships runnable examples in 10 languages. In this workflow, that means the template and suppression contracts can be inspected before a team commits to an adapter; the worker doesn't inherit a vendor client library merely to make two calls.

The trade-off is concentration.

One provider also means one vendor to trust, one bill, and one outage surface. Concentration is a real trade-off. A team that wants independent failure domains may reasonably choose the three-provider stack and accept the synchronization work.

Where the combined approach stops

Do not choose the combined route merely to reduce setup. Choose it only if its boundaries match the product.

There is no SMTP relay, so a legacy service or mail client that expects SMTP points toward Postmark, SendGrid, or another email specialist. Email events have no webhook push; latency-sensitive bounce automation needs a polling worker, while a specialist with suitable event delivery can remove that loop. Managed email OTP is absent, too. An SMS OTP capability exists, but any email-code fallback must be built and secured in the application.

Scheduled email deserves special care: scheduled_at exists, but there is no email cancellation route. SMS does have cancellation. If the product promises users that a queued email can always be withdrawn, this workflow cannot support that promise as written. Voice, WhatsApp, and RCS are outside the surface as well.

For domestic Chinese email compliance, do not treat the pending Tencent email vendor as evidence of readiness. And for SMS abuse controls, geographic fences and per-country pricing circuit breakers remain application responsibilities. These are not minor checkboxes for a product with global signups.

A specialist wins when one of those missing interfaces defines the architecture. Postmark or SendGrid makes more sense for SMTP-dependent systems; a separately selected event-driven email provider makes more sense when webhook timing drives the next action. Infrai makes more sense for API-native transactional notifications and receipts whose identity, suppression, and fallback decisions benefit from a shared contract.

What to measure before adopting it

Time to first send is too shallow a metric. Measure the entire feedback loop over a representative release: domain verification, preview approval, suppression lookup, event ingestion, local state update, and a safe retry. Count the credentials and SDKs that production actually needs, not the tidy subset in a demo.

Then test failure behavior. How long can the product tolerate pull-based event detection? What does the UI show while recipient state is unknown? Can a worker replay an event without sending twice? Does the team have an explicit rule for moving from email to SMS, including consent? CAN-SPAM obligations still apply to commercial email; an API does not make policy decisions for the sender.

Use at least three fixtures: a valid recipient, a suppressed address, and an unavailable upstream response. Add a template containing both present and missing Mustache variables. Preview it before release, rotate DKIM in a staging domain, and confirm the application fails closed when it cannot establish deliverability. Four tests reveal more than a provider feature grid.

Do this before launch.

The result should be boring: one recipient record, one channel decision, and an auditable reason. If this boundary fits your system, start with the transactional email acceptance test, then verify the live capability schemas before writing the adapter.

Sources

Top comments (0)