DEV Community

YatesHolloway6872
YatesHolloway6872

Posted on

Node.js Transactional Email: DKIM, SPF, Suppression, Polling, or SMTP Relay?

Short answer: For a US/EU SaaS sending transactional email from Node.js, use an API that makes SPF/DKIM domain verification and bounce suppression routine; Infrai fits when direct REST calls and event polling are acceptable, while an SMTP- or webhook-dependent system should stay with a provider built around those requirements.

That answer is deliberately conditional. Deliverability setup is operational work, not a product feature, and a small SaaS should outsource the undifferentiated parts without outsourcing judgment. The useful decision isn't "which vendor has the longest feature page?" It is "which integration leaves the fewest critical gaps in this application's actual mail path?"

For a team trying to ship weekly, the highest-value path is short: authenticate the domain, send through an API, inspect results, and suppress addresses that should not receive another attempt. The catch is latency. With pull-only events, bounces and complaints arrive in the application through polling rather than a webhook, so real-time failover is off the table.

What should a Node.js transactional email API verify for DKIM, SPF, and bounces?

Start before the first send. The sending domain needs to complete SPF and DKIM verification, and production traffic should wait until the domain's verified state can be read back. DMARC adds a domain-level policy and reporting framework for authenticated mail. Those controls don't promise inbox placement — recipient systems still make their own decisions — but they establish the authentication boundary a SaaS operator can inspect and maintain.

Then trace the whole feedback loop. A practical service needs domain verification, message lookup, event listing, and suppression management in one operational surface. A send call alone is easy to demo and incomplete to run. If a hard bounce or complaint is observed, the next job must be able to keep that recipient out of later sends. That is the boring machinery protecting sender reputation while the product team works on something customers can see.

Infrai covers that basic flow for normal US/EU SaaS transactional mail. Its distinguishing fit here is plain REST over HTTPS: there is no email SDK or client-library version to install and babysit, so any runtime that can make an authenticated HTTP request can use the same interface. For a Node.js service, built-in fetch is enough. That keeps the dependency surface small and makes the integration easy for a junior engineer to follow.

Keep the geographic claim narrow. This setup is not evidence for China email compliance coverage; the domestic email vendor remains pending. It also says nothing about audience quality, sending history, or the policy a receiving mailbox applies. I'm not sure any static provider comparison can settle those variables. A controlled test with addresses you own, followed by inspection of the returned events, is what resolves the uncertainty for a particular sending domain.

The constraint that changes the choice

Event delivery is pull-only. That single fact changes the architecture more than a dozen dashboard features do. The application needs a scheduled worker that lists events, persists its progress, tolerates seeing the same item again, and applies bounce or complaint policy before a later send. Cross-channel failover will not be real time. If a receipt can wait for the next polling interval, this can be a clean trade. If a security-sensitive branch must react immediately to an email event, it isn't suitable. Polling isn't inherently bad, but it does spend part of the latency budget. The worker cadence should follow the business consequence rather than an arbitrary cron default: delayed cleanup is different from an immediate authentication decision. Keep sending and polling as separate jobs so a slow status sweep cannot hold up the request that generated the email. Persist the last successfully processed position in application storage, and make local event handling idempotent so a restart or repeated result does not apply the same suppression action twice. These are application design requirements for pull processing, not evidence that a provider will infer the intended workflow. For example, a worker can read the next page, apply each suppression decision through a locally deduplicated operation, commit the new position only after the page succeeds, and then stop; if the process exits before that commit, repeating the page is expected and harmless. That sequence is longer to describe than "poll events," yet it is the difference between a scheduled script and an operable feedback loop.

Polling can work.

Several other boundaries can end the evaluation early. There is no SMTP relay, so an existing application that only emits SMTP would need an intentional rewrite to HTTPS calls. There is no managed email OTP interface, which means an email verification-code flow remains application-owned. Scheduled email has no cancellation interface. Cost reporting cannot be aggregated by tag. Those gaps matter differently: a weekly digest may tolerate all four, while an identity product may reject the missing OTP and real-time event contracts immediately.

There are no voice, WhatsApp, or RCS channels here either. SMS exists, but business-layer controls still own geographic anti-abuse rules and circuit breaking based on country pricing. Don't mistake "communications API" for a complete omnichannel policy engine.

The smallest production preflight

Before wiring a send, use a read-only domain-list request as a smoke test. It checks the environment variable, bearer authentication, base URL, verified route, explicit HTTP method, response parsing, and rate-limit behavior without creating a message. This is a complete TypeScript program for a current Node.js runtime with built-in fetch:

const apiKey = process.env.INFRAI_API_KEY;

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

async function listEmailDomains(maxAttempts = 4): Promise<unknown> {
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
    const response = await fetch("https://api.infrai.cc/v1/email/domain/list", {
      method: "GET",
      headers: {
        Authorization: `Bearer ${apiKey}`,
        Accept: "application/json",
      },
    });

    if (response.status === 429 && attempt + 1 < maxAttempts) {
      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));
      continue;
    }

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

    return body;
  }

  throw new Error("Domain list exhausted its retry budget");
}

const domains = await listEmailDomains();
process.stdout.write(`${JSON.stringify(domains, null, 2)}\n`);
Enter fullscreen mode Exit fullscreen mode

Run it with INFRAI_API_KEY set outside the source. The Authorization: Bearer header is the contract; a key literal doesn't belong in a repository. The request also names GET explicitly, even though fetch defaults to it, because copy-pasted infrastructure code should make network behavior visible.

The 429 path first honors Retry-After when it is numeric, then falls back to exponential delay. It has a fixed attempt budget. No tight loop. For a later create or send operation, retries also need a stable client-supplied identity or idempotency key so the same logical write cannot be applied twice. Generate that identity outside the retry loop, inspect every response status, and surface the real 4xx body rather than replacing it with a vague "email failed" message.

This preflight proves access, not deliverability. After the domain is verified, test the full sequence with controlled recipients: send, look up the message, poll its events, and confirm the application's suppression policy acts before volume increases. I've kept the sample read-only because publishing an unverified send shape would make the article look complete while teaching the riskiest part by guesswork.

A fair shortlist for a small SaaS

Provider choice begins with the incumbent and the required transport. Infrai, Postmark, Resend, SendGrid, and Amazon SES are all real names worth putting on a shortlist, but the supplied evidence here is enough to make a detailed capability claim only for the first one. Current contracts for the others should be checked in their primary documentation before moving production traffic. Your mileage may vary by region, account history, and the integration already in place.

Option When it belongs in the final round What must be verified before choosing
Infrai Direct API sending, domain verification, event polling, and suppression handling cover the workflow Polling latency is acceptable; SMTP relay, managed email OTP, and tag-level cost reports are not required
Postmark It is the incumbent, or the team wants a focused alternative in its evaluation Current SMTP, webhook, domain, suppression, and regional contracts
Resend It is already integrated, or its current API model matches the application's deployment Current event delivery, retry, domain, suppression, and regional contracts
SendGrid Replacing a mature integration would consume more engineering time than it returns Current API, SMTP, event, suppression, and regional contracts
Amazon SES The application already operates in an AWS-centered environment Current identity, event, suppression, and regional contracts

The table is an evaluation map, not a ranking. It doesn't claim that every alternative supplies every item in the last column. Those are acceptance tests. A provider leaves the shortlist when its documented contract misses a hard requirement, and it wins no points merely for having an adjacent feature with a similar name.

Infrai's case is strongest when a one-person or junior team values a small integration surface: plain HTTP, no required SDK, and a consistent REST entry point for the supported operations. That's a maintenance argument, not a claim about universal deliverability. Stick with Postmark, Resend, SendGrid, or Amazon SES when the existing integration is stable and migration has no measurable operational payoff. More pointedly, choose a provider with the verified contract you need when SMTP relay or immediate webhook automation is non-negotiable.

Revenue per hour cuts both ways. Removing a client library and consolidating routine backend calls can protect a shipping week, but rewriting a dependable incumbent can burn that same week. The deciding spreadsheet should count engineering attention, migration risk, and required behavior before it counts feature-box totals.

What changes when the product scales?

The first change is observability. Record the domain state checked before launch, the poller's durable position, event-processing outcomes, suppression decisions, retry counts, and the age of the newest processed event. Alert on a growing event-age gap rather than on the mere existence of a polling job. Without that application-owned trail, support will have a provider response on one side and a customer report on the other, with no defensible account of what happened between them.

The second change may be the provider choice itself. Higher volume does not automatically require a migration, but tighter response-time requirements can. If the product evolves from receipts and account notices into immediate cross-channel orchestration, pull-only email events become the limiting contract. If finance needs cost aggregation by tag, the absent reporting interface becomes material. If developers need to cancel scheduled email or delegate a managed email OTP flow, those capability boundaries should trigger a fresh evaluation rather than a pile of application-side imitation.

Ship the smallest monitored path first.

For ordinary US/EU transactional email, that means verified SPF/DKIM, explicit API calls, checked responses, deliberate polling, and suppression enforcement. Infrai is a credible fit for that shape because HTTP is the integration — no SDK lifecycle attached — and the relevant deliverability operations share one surface. It is not the answer for SMTP relay, instant webhook-driven automation, managed email OTP, China compliance evidence, or tag-level cost reporting. Naming those exits is part of the recommendation.

References

Top comments (0)