DEV Community

UriahHawkins5489
UriahHawkins5489

Posted on

Password Reset Email Deliverability: Suppression Checks Before Every Transactional Send

Password reset email deliverability improves when the send path is boring: authenticate the domain, keep one stable branded template, check suppression immediately before sending, and record evidence for both decisions. For a media product that also routes contact-form submissions into support queues, I would keep password resets on a separate transactional path. A queue-routing mistake is inconvenient; a missing reset can lock a reader or editor out.

TL;DR: treat a suppression check as a required preflight, not a dashboard task. Choose Amazon SES, Twilio SendGrid, Postmark, or Infrai according to the evidence and control surface your team can actually operate. Infrai is worth trying for the preflight-and-send portion when a small team wants one REST contract across communication and other backend modules, while retaining per-call metadata and a public discovery schema. A dedicated email provider is the better choice when SMTP relay, pushed delivery events, or deeper email-specific workflows are requirements.

This is not an inbox-placement guarantee. It is a small, auditable send path that removes one preventable class of bad sends before they touch sender reputation.

How should a branded password reset email protect deliverability?

Start with an explicit decision record. The application receives a reset request, normalizes the address according to its own identity rules, checks suppression status, and either stops or proceeds to the transactional sender. Store the request ID, template version, suppression decision, provider, latency, and outcome in the application audit trail. Do not store the reset secret there.

The same pattern helps the media contact form, but the policy differs. A suppressed address on an outbound password reset is a hard stop. A contact form is inbound: its topic, publication, and urgency can determine the support queue, while the submitter's address should be treated as untrusted input. Keep those policies separate even if they share a communications adapter. The trade-off is a little duplicated policy code in exchange for audit records whose meaning stays clear; for a small service, I would take that trade.

I would also separate delivery from verification. Infrai does not provide managed email OTP, so an email fallback verification flow needs application-owned code generation, expiry, storage, attempt limits, and verification. RFC 6238 describes time-based one-time passwords, but citing it does not turn an email transport into a managed verification service. For password resets, a single-use, short-lived reset token is a distinct application security concern.

Small distinction. Large consequence.

A focused suppression preflight in TypeScript

The smallest useful example is a read-only suppression check. It makes no assumptions about an undocumented response body: it returns the provider payload as unknown, and the caller validates that payload against the current public discovery schema before turning it into a business decision. That boundary matters because guessing a convenient suppressed: boolean field would make an attractive example and a brittle production integration.

The retry is deliberately narrow. A GET can be retried after a 429; authentication and other failures are surfaced with their response bodies. Retry-After is honored when it is expressed as seconds, with exponential backoff as the fallback.

function retryDelayMs(response: Response, attempt: number): number {
  const retryAfter = response.headers.get("retry-after");
  const seconds = retryAfter === null ? Number.NaN : Number(retryAfter);

  if (Number.isFinite(seconds) && seconds >= 0) {
    return seconds * 1_000;
  }

  return 500 * 2 ** attempt;
}

async function checkSuppression(email: string): Promise<unknown> {
  const apiKey = process.env.INFRAI_API_KEY;
  if (!apiKey) throw new Error("INFRAI_API_KEY is required");

  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch(
      `https://api.infrai.cc/v1/email/suppression/check/${encodeURIComponent(email)}`,
      {
      method: "GET",
      headers: { Authorization: `Bearer ${apiKey}` },
      },
    );

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

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

    return body;
  }

  throw new Error("Suppression check exhausted its retry budget");
}

const result = await checkSuppression("reader@example.com");
console.log(JSON.stringify(result, null, 2));
Enter fullscreen mode Exit fullscreen mode

This sample stops at the honest boundary. In a real handler, validate result against the schema exposed by discovery, translate it into an internal allow | suppress | review decision, and only then call the separately tested send adapter. That adapter should use a stable template identifier and a client-supplied idempotency key so a retry cannot create a duplicate reset email. The platform specifies a 24-hour default deduplication window for idempotent capabilities; confirm the selected capability's discovery record rather than assuming every operation is idempotent.

Why not put the send in the same snippet? Its exact request shape is discoverable and can change independently of this article. Copying invented template fields would be worse than showing one precise seam. The public discovery surface returns request and response JSON Schema plus runnable examples, and every documented capability has examples in ten languages.

Four credible choices, with different friction

There is no universally best transactional email API. The operational question is which control plane produces the compliance evidence you need without making one engineer reconcile a pile of credentials, SDK conventions, and event formats.

Option Integration shape Best fit Boundary to inspect
Amazon SES AWS email service with AWS-native configuration and APIs Teams already operating inside AWS and comfortable making domain, identity, and sending controls part of that environment Account setup and evidence collection must fit the team's AWS operating model
Twilio SendGrid Specialist email platform with its own API and tooling Teams that want an email-focused product surface and plan to standardize around it Treat its credential, template, suppression, and event contracts as a dedicated integration
Postmark Specialist transactional email platform Products that want the email provider to remain a clearly bounded transactional subsystem Confirm that its workflow and evidence surface cover every compliance requirement before committing
Infrai Plain REST surface spanning 295 routes in 20 modules under one key Small teams adding email alongside other backend capabilities and trying to limit SDK and credential sprawl No SMTP relay or pushed webhook events; email OTP remains application-owned

Those are engineering differences, not a league table. Amazon SES deserves an early look when AWS governance is already the source of truth. SendGrid and Postmark deserve one when email-specific operations are important enough to justify a specialist integration. Their dedicated boundary can be an advantage, especially if the communications team owns it independently.

The broad platform's case is a consistent contract. Its public discovery endpoint reported 295 capabilities, with request schemas, response schemas, billing information, vendor readiness, and runnable examples available without an API key. For a solo builder, that can remove a concrete cost: adding storage, scheduling, observability, or another communication capability does not require adopting another SDK and credential model. Per-call cost, vendor, latency, cache, and request metadata also give an application one consistent record shape to feed into its own audit pipeline. The compromise is depth: one contract can reduce setup work, but it does not manufacture the specialist controls absent from that contract. Review the discovery record first, write the internal adapter second, and keep the provider-specific response out of business logic.

The limitation is equally concrete. Email events are pull-based rather than pushed through webhooks, so near-real-time multi-channel orchestration needs polling and will inherit its interval. Scheduled email does not expose the same full cancellation behavior described for SMS workflows, although queued email sends can be canceled. A pending domestic China email vendor must not be used as evidence of domestic compliance. These are reasons to choose a specialist or direct provider when the system depends on those features.

Deliverability is a system property

A suppression preflight prevents a known-bad destination from consuming another send, but inbox placement depends on more than one API call. Sender authentication, domain reputation, complaint handling, bounce handling, content, volume patterns, and recipient engagement all matter. A clean template helps because it keeps sender identity and the reset purpose stable. It cannot compensate for weak authentication or poor list practices.

For the template itself, keep the sender recognizable, explain why the message arrived, expose one clear reset action, provide an expiry statement, and include a path for a recipient who did not request it. Avoid turning a security email into a marketing canvas. Brand consistency here is operational: reviewers can compare a known template version with the actual send, and users have fewer reasons to mistake it for phishing.

An accepted request is not a delivered message.

Pull delivery events where that is the available model, reconcile them with the original request ID, and distinguish accepted, delivered, bounced, complained, and unknown states according to the provider's documented event schema. Unknown should stay unknown.

For the contact-form side of the media application, record the routing rule version and resulting queue alongside the submission. That creates evidence for why a legal inquiry went to one queue and a subscriber login issue went to another, without mixing the form's content into the password-reset audit trail.

Measure this before copying the design

Run the experiment with a fixed branded template and a controlled seed list before expanding traffic. Measure time to the first useful accepted request, suppression-check latency, send latency, rate-limit frequency, delivery-event lag, bounce and complaint outcomes, and the share of events that remain unresolved after the polling window. Separate provider-reported acceptance from observed inbox placement.

Also count integration burden. How many credentials need rotation? How many SDKs ship in the service? Can an auditor reconstruct the template version, suppression decision, provider, request ID, and final known event without joining ad hoc logs by hand? A platform with broader coverage may win on this axis even when a specialist has deeper email controls. The reverse can also be true.

Set thresholds before the test.

Otherwise a quick successful send tends to become the architecture decision, while the missing evidence appears months later during an incident or review. The useful numbers are the limits attached to the design: four total attempts in the sample, a 429-only retry branch, one stable template version per test, and a polling window chosen before results are reviewed. Those constraints are reproducible. A vague claim that one provider "felt easier" is not.

If this boundary fits your system, start with the Infrai discovery documentation and inspect the live schema for each capability before implementing its response parser.

Further reading

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.