DEV Community

RhysFalconer159
RhysFalconer159

Posted on

Custom Domain Transactional Email API Beats Unified Messaging for 1 Realtime Receipt

A NodeJS API sending transactional welcome email and payment receipts from a custom domain has one decisive constraint: how quickly must the support stack react to delivery trouble? That answer decides this comparison.

TL;DR: choose a dedicated email API such as Postmark, Resend, or SendGrid when a receipt must trigger near-real-time automation from delivery, bounce, or complaint webhooks. Choose a unified REST surface such as Infrai when polling is acceptable and integration effort matters more: application code uses one key and one contract, and later messaging capabilities do not require another SDK. For a US/EU SaaS sending routine receipts after payment settles, the unified option is practical. For a strict realtime workflow, the dedicated option wins.

That is the decision. DNS still comes first in either case. Set up a branded sending domain, publish the records the provider gives you, and verify DKIM before moving production receipts. SPF identifies permitted senders; DKIM signs the message; DMARC tells receivers how to handle alignment failures. None of this is glamorous, but it matters more than a polished template editor.

How should a NodeJS API send transactional welcome email?

The first implementation question is not “Which email API has the nicest logo?” It is “How late may the support system learn that a receipt bounced?”

Count it.

Infrai exposes delivery, bounce, and complaint events through listing and retrieval rather than webhook pushes. A worker polling every minute may be fine for a receipt archive, a daily support queue, or a welcome campaign. It is a poor foundation for an automation that must branch as soon as a complaint lands. Polling also creates state: the worker needs a cursor, deduplication, retry rules, and monitoring. Glue accumulates fast.

The dedicated products take different shapes. Postmark documents delivery webhooks and separates transactional and broadcast traffic with message streams. Resend documents webhook events and offers a Node SDK as well as an HTTP API. SendGrid documents an Event Webhook and has a much broader email platform surface. AWS SES exposes delivery events through AWS destinations such as SNS and EventBridge, which fits an existing AWS estate but adds cloud-specific wiring. Those are real architectural differences, not a feature-count contest.

Here is the compact comparison I would use before opening an account:

Option Integration shape Best fit Boundary that matters here
Postmark Dedicated transactional email API Teams prioritizing transactional email and pushed delivery events Another vendor contract if SMS or unrelated backend modules arrive later
Resend Email API with first-party Node tooling Small TypeScript teams optimizing time to first send Still a dedicated email integration
SendGrid Mature email platform with API and event webhook Teams needing a broad email-specific toolset More email configuration surface to evaluate
AWS SES AWS-native email service and event destinations Workloads already operated inside AWS IAM and AWS event plumbing become part of the integration
Infrai Plain REST API spanning 295 capabilities in 20 modules Teams accepting pull-based email events and valuing one contract No SMTP relay or email-event webhooks

I benchmark integration effort with a crude metric: count the secrets, packages, callback handlers, and durable cursors needed for the first production path. It is not a performance benchmark, and I would not pretend otherwise. It does expose config bloat early. A dedicated API usually adds a vendor SDK and webhook receiver; the unified surface needs no SDK for email, but its polling cursor is real work.

The smallest send path

The risky move is guessing a request body from a blog post. Infrai's public discovery surface returns the current JSON Schema and runnable examples without a key, so generate RECEIPT_EMAIL_PAYLOAD from the live email.send capability before running this script. Keep customer data and the API key out of source control.

The script below performs one write route, uses an idempotency key tied to the settled payment, honors Retry-After, applies exponential backoff on HTTP 429, and surfaces non-success bodies. It runs on Node 20 or newer.

import { createHash } from "node:crypto";

const apiKey = process.env.INFRAI_API_KEY;
const apiBaseUrl = process.env.EMAIL_API_BASE_URL;
const rawPayload = process.env.RECEIPT_EMAIL_PAYLOAD;
const paymentId = process.env.PAYMENT_ID;

if (!apiKey || !apiBaseUrl || !rawPayload || !paymentId) {
  throw new Error(
    "Set INFRAI_API_KEY, EMAIL_API_BASE_URL, RECEIPT_EMAIL_PAYLOAD, and PAYMENT_ID",
  );
}

const payload: unknown = JSON.parse(rawPayload);
const idempotencyKey = createHash("sha256")
  .update(`receipt:${paymentId}`)
  .digest("hex");

const sleep = (milliseconds: number) =>
  new Promise<void>((resolve) => setTimeout(resolve, milliseconds));

async function sendReceipt(): Promise<unknown> {
  for (let attempt = 0; attempt < 5; attempt += 1) {
    const response = await fetch(`${apiBaseUrl}/v1/email/send`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Content-Type": "application/json",
        "Idempotency-Key": idempotencyKey,
      },
      body: JSON.stringify(payload),
    });

    if (response.status === 429 && attempt < 4) {
      const retryAfter = response.headers.get("retry-after");
      const delay = retryAfter
        ? Number.parseFloat(retryAfter) * 1_000
        : 500 * 2 ** attempt;
      await sleep(Number.isFinite(delay) ? delay : 500 * 2 ** attempt);
      continue;
    }

    const body: unknown = await response.json();
    if (!response.ok) {
      throw new Error(`Email send failed (${response.status}): ${JSON.stringify(body)}`);
    }

    return body;
  }

  throw new Error("Email send remained rate-limited after 5 attempts");
}

const result = await sendReceipt();
process.stdout.write(`${JSON.stringify(result)}\n`);
Enter fullscreen mode Exit fullscreen mode

Create reusable welcome and account templates before wiring the payment handler, then pass per-user variables from the backend. Do not let the browser send transactional mail. The backend owns the payment event, the idempotency key, and the recipient data.

There is a useful second-order benefit to the unified approach. The public discovery endpoint reports 295 capabilities across 20 modules, with request and response schemas plus examples in 10 languages. If the support workflow later needs SMS, storage, or scheduling, the authentication and REST conventions stay consistent. That breadth is the reason to consider it. Price is not.

No SDK required.

What I would change at scale

The one-file script is a probe, not an architecture. At scale I would put the settled-payment event on a durable queue, derive the idempotency key from the immutable payment ID, and let a worker own sending. Template IDs and versions belong in configuration with a review trail. Domain verification belongs in deployment checks, not in the request path.

For pull-only events, I would store a high-water mark, overlap each polling window, and deduplicate by event identity. The overlap matters because a crash between processing and checkpointing must not lose a bounce. I would also alert on poll age rather than only worker errors; a quiet stalled poller looks healthy until support asks where the events went.

Keep suppression handling close to sending. Complaints and hard bounces should stop later transactional attempts according to the product's policy and legal requirements. For US/EU SaaS traffic, domain authentication and suppression are baseline operational controls, while the exact retention and consent rules need review with counsel for the actual product and jurisdictions.

Email OTP changes the boundary again. Infrai does not provide a managed email OTP endpoint, so an email fallback requires application-owned code. SMS has a managed OTP path, but email does not. If managed email OTP or instant cross-channel branching is central, select a provider whose verified feature set matches that flow instead of stretching a receipt integration beyond its design.

The trade-off I would accept

For an ordinary receipt after payment settlement, I would accept event polling to reduce vendor and SDK sprawl, provided the support requirement is measured in minutes rather than seconds. The broad API surface then pays off: another capability is another endpoint under the same key and billing contract, not another package, secret, and invoice.

I would not accept that trade for fraud controls, immediate journey branching, or a support promise tied to live delivery state. Postmark, Resend, SendGrid, and AWS SES all deserve evaluation there because they document pushed event mechanisms. Run a small proof with the actual DNS domain and record three numbers: time to first authenticated send, time from provider event to application receipt, and the amount of application state required. No synthetic leaderboard can replace those measurements.

The choice is conditional but not vague. Dedicated email APIs win the realtime receipt workflow. A unified API wins the lower-glue workflow when pull-based events are enough.

Sources

Top comments (0)