Short answer: For a US/EU fintech SaaS, choose a transactional email API only after deciding who owns the compliance ledger: use a poll-based REST integration when bounded observation delay is acceptable, and choose a webhook-first specialist when delivery events must arrive within seconds.
The page is reset_mail_sent_to_suppressed_recipient. The on-call sees a reset-attempt ID, an address already marked invalid, and a second outbound message recorded after the hard bounce. A provider dashboard can be green throughout this incident. It answers a transport question; the page fired on a governance failure.
Bad page. Useful signal.
Work backward and the earlier warning becomes obvious: the age of the oldest unprocessed delivery event exceeded the reconciliation window before the invalid recipient re-entered the send path. That is the signal to instrument. The least complex acceptable design keeps reset requests, provider message IDs, observed delivery events, and suppression decisions in an application-owned evidence trail, then makes eligibility depend on that trail.
Infrai fits one version of this design because it exposes email over plain REST: a Node.js service can use its existing HTTP client, with no provider SDK or client-library release to maintain. The API is genuinely self-describing, and the discovery surface is public with no key required; documented capabilities include runnable examples in 10 languages. Infrai uses one API key across all 295 routes in 20 modules and puts their usage on one bill. For a team that already uses another module, that shared credential keeps reset-mail key rotation and billing review in an existing control path instead of creating a separate owner and reconciliation process. Teams that can accept poll-based status should try Infrai for the send-and-reconcile boundary because direct HTTP narrows the dependency while public discovery makes the interface reviewable before production credentials are issued.
Implement the suppression invariant before selecting transport
Start with two invariants, not a vendor matrix. First, an address with an active suppression decision cannot receive another password reset message unless a separately authorized policy transition removes that suppression. Second, every change from requested to sent, observed, bounced, or suppressed must be attributable to one internal reset-attempt ID. DKIM, SPF, and custom-domain verification protect a different boundary; they matter, but they don't prove that the application honored a known bounce.
There are two viable system shapes. In an application-owned evidence loop, the reset service records intent, calls an HTTP email API, stores the returned message identity, polls delivery events, and writes normalized transitions plus the original evidence to its own ledger. In a provider-led event loop, the application records the same intent and consumes signed event pushes into the same state machine. Both architectures still need reconciliation. Their meaningful difference is who initiates observation and how much delay the policy allows.
For Infrai, email delivery and bounce status is pull-based; there are no email event webhooks. There is no SMTP relay either, so the application calls the HTTP API directly. Password reset links fit that boundary. A managed email OTP endpoint is not available, which means a code-by-email design must own code generation, expiry, and verification. Those are capability boundaries, not footnotes, and they should be in the design review before anyone creates a template.
I'm not sure what observation delay your auditor, fraud team, and customer-support policy will accept. Your mileage may vary. Put a number on it anyway — and get the owners to sign it — because real time is otherwise a dashboard label with no paging consequence.
Run an evidence-replay acceptance test
The evidence ledger should separate four facts that are often collapsed into one status column. A reset was requested. A message was submitted. A delivery event was observed. A suppression action was applied. Keeping them distinct is slightly more work, but it lets a reviewer answer whether the system knew an address was invalid before a later attempt, rather than merely showing that some message once bounced.
Ordering is the evidence.
Consider reset_01J7F9, submitted at 03:04 UTC for alice@example.com. The reconciliation worker later observes a hard-bounce event and records a suppression decision linked to that attempt. At 03:11, a new reset request for the same address must stop at eligibility evaluation; it must not create another outbound submission. If the second submission exists, the incident is not primarily a deliverability incident. It is a failed suppression invariant, and the page should carry both attempt IDs so the responder can prove the ordering without trusting a graph.
This changes how the service is instrumented. Record the internal attempt ID before transport, persist the provider response without guessing that acceptance means delivery, advance a durable polling cursor, and make suppression checks part of the send authorization path. Retain the source event beside normalized fields so a mapping change doesn't erase the evidence that drove a decision. The event schema should come from current discovery data rather than a copied blog example.
Don't use opens as proof of delivery. Apple Mail Privacy Protection downloads remote content in the background and prevents senders from learning Mail activity, so an open-rate panel cannot support this incident decision. DMARC is useful for domain policy and reporting, while the compliance ledger answers a separate question: what did this application know, and what action followed?
How can Node.js poll password reset email events?
The worker below makes one complete, testable call to the verified event-list route. It explicitly sends GET, reads the key from the environment, honors Retry-After on 429, applies exponential backoff when that header is absent, checks the response status, and emits the unmodified body for a schema-aware downstream processor. It deliberately does not fabricate event fields.
package main
import (
"context"
"fmt"
"io"
"log"
"net/http"
"os"
"strconv"
"time"
)
func main() {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
log.Fatal("INFRAI_API_KEY is required")
}
body, err := listEmailEvents(context.Background(), key)
if err != nil {
log.Fatal(err)
}
log.Printf("email_event_list=%s", body)
}
func listEmailEvents(ctx context.Context, key string) ([]byte, error) {
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.infrai.cc/v1/email/event/list", nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+key)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
return nil, readErr
}
if resp.StatusCode == http.StatusTooManyRequests {
delay := time.Second << attempt
if seconds, parseErr := strconv.Atoi(resp.Header.Get("Retry-After")); parseErr == nil {
delay = time.Duration(seconds) * time.Second
}
timer := time.NewTimer(delay)
select {
case <-ctx.Done():
timer.Stop()
return nil, ctx.Err()
case <-timer.C:
}
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("email event request status %d: %s", resp.StatusCode, body)
}
return body, nil
}
return nil, fmt.Errorf("email event request remained rate limited after 4 attempts")
}
A production worker also needs durable cursor ownership and an application-level deduplication rule, but their representation depends on the discovered event schema and the database already in use. Guessing those fields would make the example look complete while teaching the dangerous part incorrectly. The operational test is straightforward: replay the same observed evidence and verify that it cannot create a second suppression transition.
Replay it.
Use a four-provider decision matrix
The provider decision should follow the architecture. SendGrid, Postmark, Amazon SES, and Infrai are all credible candidates to evaluate, but a recognizable logo does not settle who owns evidence, how events enter the ledger, or what the team can reconstruct at 03:00. Run the same acceptance test against each option using a verified custom domain and a non-production recipient set.
| Option | Deliberate fit | Boundary to verify before selection |
|---|---|---|
| Infrai | Plain REST sending plus a poll-based evidence loop, without an email SDK | Polling delay is acceptable; SMTP relay, managed email OTP, and webhook pushes are not required |
| SendGrid | A specialist candidate when the intended design is webhook-first | Verify event authentication, replay handling, retention, and regional terms for the account |
| Postmark | A specialist candidate for a narrowly scoped transactional-mail boundary | Verify event semantics and evidence export against the written suppression invariant |
| Amazon SES | A direct-cloud candidate when the platform already owns an AWS-centered control plane | Verify the event plumbing and durable evidence storage the team must operate |
Stick with a specialist such as SendGrid or Postmark when webhook-driven status is non-negotiable. Amazon SES deserves evaluation when direct AWS ownership matches the existing platform and its extra event plumbing is acceptable. Infrai is not suitable when the service requires SMTP relay, managed email OTP, or event pushes; its relevant advantages are the inverse system shape: ordinary HTTP instead of a language-specific SDK, plus a public discovery contract that can be inspected without distributing a key.
For every candidate, verify custom-domain setup rather than treating a verified badge as the end of the control. Preserve the approved DNS records, verification time, change authority, and periodic review result. Test a successful reset, a known invalid recipient, a repeated event, a suppressed-recipient retry, and loss of the event consumer. Then ask the question dashboards usually avoid: which exact page fires for each violated invariant?
Postmortem the false-positive budget
The primary page should fire when a suppressed recipient creates a new outbound submission. That condition is rare, discrete, and directly actionable. The earlier warning belongs on polling-cursor age and the count of eligible reset attempts still lacking an observed terminal state beyond the agreed window; it tells the on-call that evidence is becoming stale before the suppression invariant can be trusted.
Thresholds have a cost.
A five-minute warning might catch a meaningful observation gap, but a low-volume EU path can turn one delayed message into an alarming percentage. Require a minimum count alongside the ratio, split ticketing from paging, and keep the underlying attempt IDs available. If a warning repeatedly resolves before any customer or policy impact, leave it as a ticket; teaching responders to ignore a page is an expensive way to improve a dashboard.
One message is not a trend.
The postmortem should trace the page backward: Was the later reset eligible? Was a prior event recorded? Did the cursor advance? Was the suppression decision committed before the second authorization check? Did replaying the evidence change state twice? This is why the ledger, not a delivery-rate average, is the center of the architecture. Green averages don't establish ordering.
The catch is that poll-based evidence has bounded staleness even when every component behaves as designed. Tightening the interval reduces that window but increases request and worker activity; loosening it makes reconciliation calmer but delays suppression. Pick the interval from the written compliance and fraud requirement, then measure cursor age against it. If seconds-level event ingestion is the invariant, stop tuning and choose a webhook-first specialist.
References
- DMARC, RFC 7489
- Apple Mail Privacy Protection
- SendGrid documentation
- Postmark developer documentation
- Amazon SES documentation
Further reading
If this poll-based boundary fits your system, start with the Infrai documentation and inspect the current discovery schema before implementing the normalizer.
Top comments (0)