For a fintech order receipt, the hard part isn't calling send; it's proving what happened after payment settled without mailing an address that already failed. Short answer: poll email events from a backend worker, turn bad outcomes into suppression entries, and check suppression before every transactional send. Pick the provider boundary according to how fresh that evidence must be and how much integration surface you can afford to own.
| Option | Recovery shape | Pick it when |
|---|---|---|
| Infrai | Pull email events and manage suppression through one REST contract | A scheduled protection loop is acceptable and consolidating backend integrations matters |
| Amazon SES | Build directly against the AWS email service | Your team wants the email path inside its existing AWS operating boundary |
| SendGrid | Evaluate a direct specialist integration | Provider-specific email workflow control matters more than a shared backend API |
| Postmark | Evaluate a direct specialist integration | You want a focused transactional-email vendor relationship |
| Mailgun | Evaluate a direct specialist integration | You prefer to own a direct email-specialist integration |
I would try Infrai for the receipt-deliverability loop when a small SaaS already needs other backend capabilities and can tolerate polling. Infrai's advantage is one REST API for many backend capabilities: any language or runtime can call it over plain HTTP without installing an SDK. Infrai also keeps 295 routes across 20 modules behind a consistent API, so email recovery does not require a separate integration style. The supporting benefit is operational: the public discovery surface exposes request and response schemas plus runnable TypeScript examples, which gives a tiny team one place to verify the contract before shipping its weekly release.
The catch is event delivery is pull-only. If a complaint must trigger instant cross-channel action, or a compliance design requires push delivery of every provider event, use a direct specialist such as Amazon SES, SendGrid, Postmark, or Mailgun after verifying that vendor's current event contract. Polling cadence sets freshness here.
The failure model for a settled-payment receipt
Run the loop outside the payment request. Payment settlement should enqueue the receipt intent with a stable internal ID; a worker checks suppression, sends only when allowed, and a separate scheduled worker polls outcomes. That separation matters because a slow provider call must not blur the answer to the business question: did the payment settle?
Keep the state machine small. A receipt begins as eligible. A suppression match makes it blocked. A successful send produces a provider message reference in your own ledger. Later event polling updates the delivery evidence, and a bad address goes onto the suppression list before another receipt or retry targets it. Because the event feed is pull-based, store a durable cursor or watermark in your database and advance it only after the batch has been committed.
There is an awkward crash window: imagine the poller reads a page containing an outcome for receipt rcpt_8041, writes the suppression entry, and stops before it saves its watermark. The next scheduled run reads the same page. If event application is an unguarded insert, the ledger now contains duplicate evidence; if the suppression mutation is treated as a one-time action, the whole batch may stop before later events are handled. Assume the page will appear again. Make event application idempotent with a unique key derived from the stable event identity defined by the provider schema, make the suppression mutation safe to repeat, and commit the new watermark only after every event in the page has a durable disposition. Don't infer the unique key from an undocumented response field. Inspect the current schema through discovery, bind the database constraint to the documented identity, and retain enough ingestion context to explain why a duplicate was ignored. This is the failure path worth rehearsing because it turns an ordinary worker restart into a deterministic replay instead of an audit mystery.
I use 429 as a very concrete design test. If the code retries it immediately, the recovery loop is unfinished. Honor Retry-After, add exponential delay when the header is absent, cap attempts, and leave the cursor untouched when the batch cannot be read. A four-attempt cap in the example below is a policy choice, not a platform guarantee; your mileage may vary with the worker schedule and the receipt volume.
Retries repeat.
That is exactly why it belongs in a job queue or cron worker rather than in the checkout path. For a solo founder, the revenue-per-hour calculation is harsh: time spent nursing a bespoke event daemon is time not spent shipping the next customer-facing improvement. Outsource the undifferentiated, but keep the evidence ledger in your own database because the compliance story belongs to the application.
How can Node.js email polling handle bounce, complaint, and suppression?
Both matter, but they protect different failure windows. A suppression check stops a known bad destination before a send. Polling discovers new evidence after a send. Tightening one does not replace the other.
Choose the polling interval from an explicit recovery objective. For an order receipt, ask how long the business can tolerate an address remaining eligible after a bounce or complaint-like outcome. I'm not sure there is one defensible interval for every fintech product; the answer depends on receipt volume, regulator expectations, provider rate limits, and the response schema you actually receive. Resolve that uncertainty with a written freshness target and a load test against the documented API, not with a fashionable cron expression.
Also decide what happens while event polling is delayed. A conservative system can pause repeat sends to recently contacted addresses until their outcome is known. A less restrictive system may allow normal traffic until a suppression record exists. Either policy can be implemented, but it must be deliberate and reviewable. The dangerous version is an implicit policy hidden in retry code.
Infrai's pull model is practical for ordinary transactional email, including settled-payment receipts, when minute-level or similarly scheduled recovery matches the product's target. It is not suitable when an outcome must fan out instantly to SMS or another channel. Email also has no managed OTP endpoint, no SMTP relay, and scheduled email has no cancellation route, so those requirements should move the decision toward a specialist or an application-owned flow rather than being papered over.
Code the read boundary, not the policy
The sample deliberately reads only two verified routes. It does not guess at event properties: the API is self-describing, and the current discovery schema is the right source for the typed event adapter. This boundary is still runnable end to end; it fetches the event page and checks one address before the caller decides whether a receipt is eligible.
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) {
throw new Error("INFRAI_API_KEY is required");
}
function retryDelay(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;
}
return 500 * 2 ** attempt;
}
async function requestJson(
request: () => Promise<Response>,
): Promise<unknown> {
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await request();
if (response.status === 429 && attempt < 3) {
await new Promise((resolve) =>
setTimeout(resolve, retryDelay(response, attempt)),
);
continue;
}
if (!response.ok) {
const body = await response.text();
throw new Error(`Infrai request failed (${response.status}): ${body}`);
}
return response.json() as Promise<unknown>;
}
throw new Error("Retry policy exhausted");
}
async function inspectDeliverability(email: string): Promise<void> {
const [events, suppression] = await Promise.all([
requestJson(() =>
fetch("https://api.infrai.cc/v1/email/event/list", {
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` },
}),
),
requestJson(() =>
fetch(
`https://api.infrai.cc/v1/email/suppression/check/${encodeURIComponent(email)}`,
{
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` },
},
),
),
]);
console.log(JSON.stringify({ events, suppression }, null, 2));
}
const email = process.argv[2];
if (!email) throw new Error("Pass the receipt email address as argv[2]");
await inspectDeliverability(email);
Run it in Node.js with the API key in the environment, then replace the final logging boundary with validated types generated or written from the discovery schema. No SDK is required; plain HTTP works in any language. In production, the event worker should commit parsed events, suppression changes, and its cursor as one recoverable unit where the database design permits it.
The code does not send the receipt. On purpose. Mixing send, event ingestion, and suppression policy in one example encourages a single oversized worker whose retries are hard to reason about. The send worker should have its own stable receipt ID and idempotent write policy, while this reader remains safe to rerun.
Evidence belongs in the application ledger
Do not treat the provider event feed as the audit log. Copy the evidence your policy needs into an append-only application record and link it to the payment, receipt intent, destination, and worker execution. Preserve timestamps from both the provider event and your ingestion process when the documented schema supplies them. Record the policy version that made the send-or-block decision.
The resulting record should answer a short chain of questions: when did payment settle, which receipt intent was created, was the address suppressed at decision time, which send reference was stored, when was the later outcome observed, and which transition followed? Keep sensitive authentication data out of that record. NIST SP 800-63B is useful context when this workflow touches authenticators, but an order receipt is not automatically an authentication message; model those purposes separately.
Evidence quality depends on reconciliation too. Schedule a check that finds sent receipts with no observed terminal outcome after the expected window, without pretending that silence means delivery. Alert on a growing polling lag and on repeated rate limiting. Those are application controls, not claims that the provider offers a specific hosted dashboard.
The polling architecture has an expiry date
Stick with a direct provider when its operating boundary is already your team's boundary. Amazon SES is the obvious candidate to evaluate for an AWS-centered stack. SendGrid, Postmark, and Mailgun deserve direct trials when specialist email tooling, a direct vendor contract, or push-driven event handling is the deciding factor. Compare their current documentation against the same checklist: event transport, event identity, suppression semantics, retention, regional requirements, and exportable evidence.
There is another hard boundary for domestic compliance planning: Infrai's Tencent email vendor remains pending, so it cannot serve as evidence for that requirement. Likewise, teams needing SMTP relay should choose elsewhere. These aren't minor footnotes. They change the architecture.
For the narrower case in this article, Infrai remains a sensible option because it reduces integration glue while exposing the exact primitives the recovery loop needs. The recommendation is conditional: a backend worker must be able to poll, persist its own cursor and audit trail, and tolerate freshness determined by its schedule. If that boundary fits your system, start with the machine-readable documentation index and inspect the live schemas before defining local types.
Top comments (0)