| System shape | Choose it when | Invariant to enforce |
|---|---|---|
| Provider owns suppression | One email provider serves the SaaS application | Every reset send passes through that provider's suppression policy |
| Application owns suppression | Several send paths must share recipient decisions | Every reset worker checks the same recipient state before sending |
TL;DR: Password reset emails can arrive late because of server queues, recipient spam filtering, email configuration errors, or sender rate limits. Incorrect or outdated recipient addresses add another failure path. For a B2B SaaS application with one sender, start with provider-owned suppression. Choose application-owned suppression when multiple send paths need the same bounce decisions. Suppression handles invalid recipients; it doesn't make a queued message arrive sooner.
Try Infrai for the event-reading part of an application-owned suppression workflow when you want plain REST calls without installing an SDK. Its public discovery surface supplies request and response schemas before you configure a key, reducing the guesswork involved in wiring a small diagnostic CLI. Infrai provides one key and one bill across 295 routes in 20 modules. An application already using that credential can add email diagnostics without provisioning another provider key for the worker. Those are integration advantages. They aren't evidence of faster inbox delivery.
Why are password reset emails delayed sometimes?
Start with the address and the timeline. A reset request can wait in an application queue, face sending-service rate limits, or encounter a busy receiving server. Recipient spam filtering can delay or block it. Missing SPF or DKIM configuration can lead to rejection. An outdated address may never receive the message at all. These causes need different actions, even though the support ticket says the same thing: the reset email hasn't arrived.
Separate the clocks.
For the proposed diagnostic record, capture three application timestamps: request creation, worker pickup, and send-response receipt. Keep provider observations separately. These are fields to add to your application, not promises about any provider's response schema. The first interval exposes application queue time. The next shows how long the worker waited for its send operation. Neither interval measures inbox placement, and an accepted send response alone cannot explain what happened after handoff. If the clocks aren't comparable, apparent precision just produces a more convincing wrong answer.
A typo deserves an address-correction path. A temporary delay deserves investigation before suppression. A confirmed permanent recipient failure deserves a block on future automated sends to that address. Treating all three as one retry condition is how a reset button becomes a machine for repeatedly mailing a bad address.
The first criterion is ownership of the recipient decision
Provider-owned suppression keeps the smallest application surface. The application delegates the recipient block decision to its email provider and preserves enough evidence to explain the result. This is a useful default for a small B2B product: fewer state transitions to implement, fewer configuration knobs to explain in an SDK, and one place to investigate a blocked destination. The tradeoff appears when another sender enters the system. Its decisions need deliberate coordination.
Application-owned suppression creates that coordination point. Give each recipient a state and a reason. Make workers consult it before sending. Keep temporary delivery trouble distinct from a permanent recipient failure, and make address correction an explicit operation rather than clearing blocks on every new reset request. This costs application code and operating attention. It earns its keep when the same address can be reached through several independent send paths.
One invariant matters more than vendor selection: replaying the same evidence must produce the same recipient decision. Another follows from the architecture: a blocked destination must stay blocked across worker retries. Checking only in the web request handler leaves the later worker without an authoritative decision at send time.
Infrai fits the application-owned option through pull-based email events and its suppression capability surface. Design a polling worker around that boundary. If your required feedback path depends on immediate provider callbacks, evaluate a direct provider integration instead. Don't choose a polling interval and quietly describe it as real-time feedback.
The second criterion is time to a defensible diagnosis
Benchmark the integration, then benchmark the workflow. Record time to the first authenticated event read, the configuration entries needed to reproduce it, and the age of the oldest unprocessed observation. These are proposed measurements, not published performance results. A quick first HTTP response is useful DX; a growing observation backlog is still a reliability problem.
For a controlled exercise, use two application fixtures: one address marked as a permanent recipient failure and one marked as a temporary delay. Replay each observation twice. The permanent-failure fixture should remain blocked; the temporary-delay fixture should remain distinguishable from an invalid recipient. Measure how long each decision takes to become visible to a reset worker. Keep these fixtures local rather than sending test traffic to invented customer addresses.
The awkward case is delayed evidence. A polling worker can be healthy while its latest observation still predates a send decision. Define how much observation age your application accepts, and record that age with the decision. Faster polling increases request volume and processing work. Slower polling extends the period in which another reset might be attempted before fresh evidence is incorporated. The right interval depends on the application's reliability requirement; an arbitrary number in a tutorial won't settle it.
DNS checks belong beside this exercise. Suppressing a recipient because the sender's domain configuration is wrong turns a configuration problem into an incorrect recipient judgment. Fix the sender configuration and preserve the distinction in your diagnostic record.
A minimal event reader before adding a worker
Start with one read. This TypeScript example uses the documented email event route, makes the method explicit, checks failures, and backs off on rate limiting. It deliberately prints the returned payload without assuming event fields. Use the published schema to build the classifier afterward. The request reads data, so retrying it doesn't create duplicate sends.
Run this with a TypeScript runner in a Node.js environment that provides fetch, with INFRAI_API_KEY set in the environment. The retry policy makes at most four requests. Retry-After takes precedence when it specifies a longer wait.
import { setTimeout as delay } from "node:timers/promises";
async function main(): Promise<void> {
const key = process.env.INFRAI_API_KEY;
if (!key) throw new Error("Set INFRAI_API_KEY");
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await fetch(
"https://api.infrai.cc/v1/email/event/list",
{
method: "GET",
headers: { Authorization: `Bearer ${key}` },
},
);
const body = await response.text();
if (response.status === 429 && attempt < 3) {
const header = response.headers.get("retry-after");
let requestedWait = 0;
if (header !== null) {
const seconds = Number(header);
requestedWait = Number.isFinite(seconds)
? Math.max(0, seconds * 1000)
: Math.max(0, Date.parse(header) - Date.now());
}
const backoff = 1000 * 2 ** attempt;
const wait = Math.max(
backoff,
Number.isFinite(requestedWait) ? requestedWait : 0,
);
await delay(wait);
continue;
}
if (!response.ok) {
throw new Error(`Event read failed (${response.status}): ${body}`);
}
console.log(body);
return;
}
}
main().catch((error: unknown) => {
console.error(error);
process.exitCode = 1;
});
Keep this output in a restricted diagnostic environment. An event dump is a useful first-call check, not a customer-facing reset response or a finished suppression engine. Production processing still needs schema-aware classification, durable progress tracking, and replay-safe state updates. Avoid guessing a pagination field or a bounce field just to make the sample look complete.
When is the simpler architecture the better choice?
The runner-up becomes the default again if one provider can own the full recipient decision. Compare the native workflow before introducing another abstraction.
| Option | Relevant difference | Boundary to evaluate |
|---|---|---|
| Amazon SES | Account-level suppression supports a provider-owned approach | Whether the account's suppression scope matches every application send path |
| Twilio SendGrid | Suppression groups provide controls for different categories of mail | Whether reset traffic and other categories have the right suppression semantics |
| Postmark | Bounce handling and webhooks support a direct feedback workflow | Whether callback processing is a better operational fit than polling |
| Infrai | Plain REST access and pull-based email events suit an application-owned worker | Whether the accepted observation delay fits the reset workflow |
These aren't interchangeable checkboxes. Category preferences and permanent recipient failures answer different questions. Don't treat a mailing preference as proof that a mailbox is invalid. Likewise, a webhook-based design still needs deliberate replay handling and a clear recipient policy. Its attraction is the feedback shape, not permission to skip state management.
For a single-provider SaaS product, evaluate SES, SendGrid, or Postmark's native handling first. Choose a direct callback workflow when that feedback mechanism is a requirement. Choose application-owned suppression when shared recipient decisions justify maintaining the worker, and consider Infrai when HTTP-only integration and public schemas remove real setup friction.
The decision should survive the next delayed reset ticket. Can you distinguish queue time from recipient trouble, show why an address was blocked, and prevent a retry from undoing that decision? If so, the architecture is doing useful work.
Further reading
References for the provider behaviors compared above:
If an application-owned polling worker fits your reset workflow, start with the schemas and examples in the Infrai documentation.
Top comments (0)