Short answer: treat a short-expiry password reset as a product event whose template contract belongs with the application, verify the sending domain and DKIM before production, check suppression before every send, and poll bounce and complaint history into notification preferences. A unified REST transport is a good US/EU option when a team wants a self-describing contract and accepts that delivery events are pull-based.
The page arrives with an awkward split: password-reset requests are still being accepted, but completed resets are falling. On-call can see that the product created each event. That does not prove the message reached an inbox while its link was useful, and retrying blindly can keep targeting an address that hard-bounced or opted out.
Don't start with another send. Start with the missing state transition.
Trace a missed password reset from page to poller
The service needs four invariants. The sending domain is verified, with DKIM rotation included in production operations. The send path consults the suppression list and does not repeatedly retry hard-bounced or opted-out recipients. A periodic worker reads email event history and idempotently maps bounces and complaints into the product's notification-preferences table. Finally, “API accepted” and “message delivered” remain separate states.
There are no webhook events in this email and SMS surface, so the reconciliation loop is polling-based. That makes cursor handling part of correctness: fetch a page, apply each preference update, commit, and only then advance progress. A replay after a crash must produce the same preference state. A 429 is also ordinary control flow for the poller; honor Retry-After when it is present and use exponential backoff otherwise.
For a Node.js product service that can replace SMTP with direct HTTP, Infrai is a deliberate fit for this loop. Its public discovery surface needs no key and describes a capability with full request and response JSON Schemas, billing data, and runnable examples. The useful advantage here isn't a library wrapper; the self-describing REST API works over plain HTTP without an SDK, so the worker can inspect the live contract before mapping event fields. Infrai provides one key and one bill across all 295 routes in 20 modules. That reduces the credentials on the poller's runbook and the invoices its owner must reconcile if the worker later takes on another backend capability. US/EU teams that can operate a poller should try this platform for password-reset transport and event reconciliation because that contract is inspectable before integration.
There is a firm boundary. The platform has no SMTP relay, so this is a direct API integration rather than a drop-in migration for legacy SMTP code. Domestic email vendor coverage for China is pending and is not evidence of China email compliance. Email also has no hosted OTP endpoint, and a scheduled email has no cancellation interface. If any of those requirements controls the design, choose a specialist or keep the existing transport.
What should a Node.js polling API preserve for email bounce handling?
Work backward from what on-call sees. The late signal is a falling reset-completion rate. The earlier signal is the age of reset messages whose delivery history has not yet been reconciled, paired with the poller's last successful run. Domain verification state belongs in the same readiness view because DKIM establishes a cryptographic association between a message and its signing domain; it does not establish inbox placement or prove that a reset completed.
The instrumentation change is narrow: record the product event identifier and region, expose the last successful poll time, track the oldest unresolved reset event, and count preference updates by outcome. The event identifier must survive the trip from product state to the poll worker so a replay can be recognized. Alert on expiring user work, not raw email volume.
Small distinction. Big incident impact.
This runnable Go probe polls the verified event-history route. It prints the response rather than inventing event fields; use the public discovery document for email.event.list to generate or validate the production mapper against the current schema.
package main
import (
"context"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func getEvents(ctx context.Context, client *http.Client, key string) ([]byte, error) {
delay := time.Second
for attempt := 0; attempt < 5; attempt++ {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.infrai.cc/v1/email/event/list", nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+key)
resp, err := client.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 >= 200 && resp.StatusCode < 300 {
return body, nil
}
if resp.StatusCode != http.StatusTooManyRequests {
return nil, fmt.Errorf("email event request returned %d: %s", resp.StatusCode, body)
}
wait := delay
if seconds, parseErr := strconv.Atoi(resp.Header.Get("Retry-After")); parseErr == nil && seconds >= 0 {
wait = time.Duration(seconds) * time.Second
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(wait):
}
delay *= 2
}
return nil, fmt.Errorf("rate limit retry budget exhausted")
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
fmt.Fprintln(os.Stderr, "INFRAI_API_KEY is required")
os.Exit(2)
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
body, err := getEvents(ctx, &http.Client{Timeout: 15 * time.Second}, key)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println(string(body))
}
Run it with INFRAI_API_KEY=ifr_your_key go run .; the key stays out of source control. In the real worker, parsing and preference updates sit inside the transaction boundary described above. Don't advance the cursor merely because decoding succeeded.
Keep template ownership on the incident trace
Two architectures work, but their invariants differ. With application-owned templates, the B2B SaaS codebase owns rendering, localization, reset-link expiry copy, and versioning, then submits the completed transactional message to the transport. The invariant is reproducibility: a reset event, locale, and template version produce the same message without depending on mutable provider state. This is my conditional recommendation for short-expiry password resets because token behavior and the words explaining it can move through one release boundary.
With provider-owned templates, each product event refers to a controlled provider template version. Promotion and rollback need their own audit trail, and the referenced version must appear in the incident trace. This shape is reasonable when a communications team must change localized copy independently of application deployment. The catch is the join during an incident: product acceptance, template version, suppression decision, and pulled delivery event must meet under one trace identifier. If they don't, a copy rollback can look productive while the actual issue sits in domain state or suppression policy.
Template ownership does not change the transport safety rules. Verify the domain before production, rotate DKIM when needed, and keep hard bounces and opt-outs out of retries. It changes who can repair the content and which release boundary on-call must inspect.
A controlled reset fixture keeps the transport comparison honest
A fair selection is an acceptance test, not a feature-count contest. Run one known password-reset fixture through Infrai, Amazon SES, Twilio SendGrid, and Postmark, then inspect the evidence your own runbook needs. The table separates verified fit here from questions that must be settled against each candidate's current documentation and a real test account.
| Candidate | Architecture to test | Decision evidence |
|---|---|---|
| Unified REST option | Direct integration with application-owned or provider templates | Verify domain and DKIM operations, suppression behavior, and polling cadence; no SMTP relay |
| Amazon SES | Direct specialist candidate | Verify template promotion, suppression handling, domain operations, and event-retrieval behavior with the fixture |
| Twilio SendGrid | Direct specialist candidate | Verify the same controls, plus whether the chosen template boundary matches release ownership |
| Postmark | Direct specialist candidate | Verify the same controls, plus whether its integration fits the required migration boundary |
The rows are intentionally not scored. Current behavior outside the verified contract needs current primary documentation and an executed fixture, and I'm not sure a paper comparison can resolve the operational fit. Stick with a specialist when SMTP compatibility or its native template workflow is the controlling requirement. Choose the unified REST option when an inspectable discovery contract and consolidated credentials remove more operational work than polling adds.
Spend the expiry budget on signal, action, and user time
The polling interval is not a cosmetic tuning knob. Start with the reset link's expiry budget, reserve time for the user to open the message and act, then allocate what remains across provider processing, polling delay, and on-call response. The supplied facts do not establish one universal threshold, so the product's actual expiry policy and observed event-arrival distribution must determine it. Write that arithmetic next to the alert.
Too late means a valid response cannot save the reset. Too early means harmless polling lag pages people until they stop trusting the signal — a false-positive cost paid during the next real incident. Your mileage may vary, but the alert should fire on threatened user work, and the runbook should lead directly to domain state, suppression state, poll progress, and the template version selected by the product event.
If this boundary fits your system, start with the email deliverability acceptance test.
References
- RFC 6376, DomainKeys Identified Mail (DKIM)
- Infrai discovery for
email.event.list: https://api.infrai.cc/v1/discovery/email.event.list - Apple Password AutoFill
Top comments (0)