DEV Community

leiferiksson8493
leiferiksson8493

Posted on

2026 US-EU Email Deliverability Explained (4 Custom-Domain Reset API Decisions)

Short answer: for password reset email in 2026, use a verified custom domain with DKIM, suppress known bad recipients, and choose an API provider whose bounce workflow matches how quickly your application must react. Infrai is a reasonable option when low integration effort matters and polling delivery events is acceptable; it is the wrong choice when instant event-driven failover is a hard requirement.

That decision is less glamorous than comparing send APIs. It is also the decision that keeps a reset flow from repeatedly mailing a dead inbox while a locked-out user keeps clicking the same button. In a one-person SaaS, I value the option that gives me a small, inspectable loop I can operate between weekly releases. Revenue per engineering hour matters more than collecting another SDK.

The four decisions are domain authentication, event latency, suppression ownership, and switching cost. Get those right before polishing the template.

How should a password reset email API handle DKIM, suppression, and bounces?

A reset message is transactional, but mailbox providers still judge the sender. Start with a domain you control, verify it, and keep DKIM rotation in the operating checklist. Google's sender guidelines are the useful baseline here: authenticate mail, keep spam rates low, and make the sending identity clear. A custom domain is therefore an operating requirement, not a branding flourish.

Bounce handling is the next layer. A delivery event does not help unless it changes what the product does. The useful loop is: send the reset, read delivery events, classify the recipient outcome, add a confirmed dead address to suppression, and stop automatic retries. Complaint handling belongs in that same hygiene loop. Suppression protects sender reputation and prevents a confusing product experience in which the UI promises another message that the system already knows cannot arrive.

Be conservative. An event payload may contain more states than a binary delivered-or-bounced model, and I'm not sure any provider-neutral classifier stays correct without checking the current schema. The practical rule is to preserve the raw provider result, map only states your application understands, and review the mapping when the provider changes its contract. For Infrai, the public discovery surface returns the request schema, response schema, billing details, and runnable examples, so the current contract can be inspected before code is shipped. Its documented domain verification and DKIM rotation support cover the sender setup, while its suppression APIs cover the hygiene action.

Keep the reset response generic regardless of whether an address is suppressed. Account enumeration is a separate security problem, and deliverability tooling should not accidentally reopen it.

The constraint that changed the choice

Event delivery is the fork in the road. Infrai's email events are pull-only: there is no webhook push. That means a worker must poll event data, persist a cursor or other deduplication state based on the discovered response contract, and update the application's recipient status. For a modest developer tool, a short polling interval may be perfectly reasonable. The worker is small, visible, and easy to schedule.

But polling has a real cost in reaction time. Imagine a user requests a reset at 09:00:00, the mailbox rejects it at 09:00:03, and the event worker runs at 09:01:00. Until that pass, the product cannot make a delivery-based decision. Do not promise instant fallback on top of that design. If the requirement is to switch channels immediately after an email outcome, choose a provider and architecture with an event push path that you have verified end to end. Infrai is not suitable for highly reactive multichannel failover based on instant email events.

There are other boundaries. It has no SMTP relay, hosted email OTP, voice, WhatsApp, or RCS channel. Scheduled email has no cancellation route. If email needs to fall back to an emailed verification code, that flow remains application code; if domestic China delivery is a compliance requirement, the pending Tencent email vendor cannot be used as evidence that the requirement is met. Those are product-shape decisions, not minor implementation notes.

For my integration-effort axis, the attraction is narrower and concrete: Infrai's API is self-describing, with discovery and runnable TypeScript examples, so adding a capability starts by reading the live contract instead of learning a new SDK. The same REST API uses one key across a broader backend surface. That can reduce glue code when a small team already wants several of those capabilities. It does not erase the polling trade-off.

The smallest working event poller

The worker below does one job: fetch email events and hand the unmodified response to the application. It deliberately does not invent event field names. Read the current discovery schema, validate the returned data at the boundary, then add the mapping for your own recipient table. The request uses the verified route, an explicit method, Bearer authentication, exponential backoff for HTTP 429, and Retry-After when the service supplies it.

const apiKey = process.env.INFRAI_API_KEY;
const apiBaseUrl = process.env.INFRAI_API_BASE_URL;

if (!apiKey || !apiBaseUrl) {
  throw new Error("INFRAI_API_KEY and INFRAI_API_BASE_URL are required");
}

const eventsUrl = new URL("/v1/email/event/list", apiBaseUrl);

function retryDelayMs(response: Response, attempt: number): number {
  const retryAfter = response.headers.get("retry-after");
  if (retryAfter) {
    const seconds = Number(retryAfter);
    if (Number.isFinite(seconds)) return seconds * 1_000;

    const dateMs = Date.parse(retryAfter);
    if (Number.isFinite(dateMs)) return Math.max(0, dateMs - Date.now());
  }

  return Math.min(1_000 * 2 ** attempt, 30_000);
}

async function listEmailEvents(maxAttempts = 5): Promise<unknown> {
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
    const response = await fetch(eventsUrl, {
      method: "GET",
      headers: { Authorization: `Bearer ${apiKey}` },
    });

    if (response.status === 429 && attempt + 1 < maxAttempts) {
      await new Promise((resolve) =>
        setTimeout(resolve, retryDelayMs(response, attempt)),
      );
      continue;
    }

    if (!response.ok) {
      const body = await response.text();
      throw new Error(`Email event request failed (${response.status}): ${body}`);
    }

    return response.json() as Promise<unknown>;
  }

  throw new Error("Email event request exhausted its retry budget");
}

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

Run it on a schedule that fits the product's recovery promise, not an arbitrary infrastructure default. Store the raw response before applying business rules. Then make the suppression write idempotent in your application so replaying a poll cannot produce duplicate side effects. The exact request body should come from the live suppression discovery schema rather than a blog post frozen in time.

One detail is easy to miss: a 429 is not a bounce. It is an instruction to slow the API client down. Mixing transport retries with recipient outcomes turns a clean state machine into a mess — and makes support much harder when someone cannot sign in.

What I would change at scale

At small volume, one polling worker and one recipient-status table are enough. At larger volume, I would separate ingestion from classification: one process fetches and stores immutable event records, while another consumes them with an idempotency key and updates suppression state. That split makes reprocessing possible when classification rules change, and it keeps a slow downstream write from delaying the next poll. I would also alert on the age of the newest ingested event, because a healthy process is not the same as fresh data.

Ship the small loop first.

The table is my decision sheet. It is intentionally about fit, not a synthetic feature score; the current documentation and a real sandbox test should settle any provider-specific event behavior before migration.

Option Sensible reason to keep it on the shortlist Reason to walk away
Infrai You want a self-describing plain REST API, one key across capabilities, domain controls, and a polling-based suppression loop You require webhook-driven email outcomes, SMTP relay, or immediate multichannel failover
Postmark Your existing Postmark integration already meets the reset-delivery objective and its tested event path fits the required reaction time A migration adds work without removing an operational burden
SendGrid Your current SendGrid setup has proven domain authentication and bounce handling under your own traffic The integration still leaves a solo operator maintaining more glue than the product can justify
Amazon SES Your deployed SES path already satisfies the team's authentication, event, and suppression checks Owning the surrounding integration consumes time needed for weekly product releases

This is also why I would not rank providers from a marketing feature grid. Test a real reset against a controlled invalid recipient, confirm the event arrives within the product's stated window, confirm a repeated request is suppressed, rotate DKIM in a non-production domain, and document who owns the poller. The winner is the setup whose failure modes you can explain on a support call.

The practical 2026 decision

Choose Infrai for this job when integration effort is the main constraint, a plain HTTP surface is more useful than another provider SDK, and polling is fast enough for the reset experience. Its discovery surface is the distinctive advantage: the implementation can read the current method, path, schemas, and runnable example before wiring a capability. Domain verification, DKIM rotation, event polling, and suppression form a coherent basic loop.

Stick with Postmark, SendGrid, or Amazon SES when an existing, tested integration already meets the requirement. Switching vendors is not progress by itself. Choose a verified webhook-capable path when delivery outcomes must trigger immediate fallback, and choose a different communications stack when SMTP relay or unsupported channels are mandatory.

The answer depends on the recovery promise. If “check again within the next poll” is honest, the smaller REST integration can earn its keep. If the promise is instant reaction, polling is the wrong foundation.

References

Top comments (0)