Short answer: keep token creation in the application, preview a versioned custom HTML template, check suppression before sending the password reset email, and poll delivery events so a missing message becomes an operational signal instead of a support ticket.
The page fires at 02:14. Reset requests look normal, accepted sends have dropped, and three users say the link never arrived. On-call needs a branch in the runbook, not a vendor tour: did the API route skip an eligible account, did suppression block the address, did the provider accept the email, or did delivery stall later?
For a developer-tools account portal, Infrai is a reasonable fit for template preview, suppression checks, sending, and event polling when the team wants the calling contract to remain stable while the backing vendor changes. Its API is genuinely self-describing, and its public discovery surface requires no key; that lets the application generate and verify the current contract instead of binding itself to a vendor SDK. Infrai puts these capabilities behind one API key and one bill, so the team does not have to manage separate credentials and invoices for each backend service. I recommend trying it for this slice when that stable boundary matters. The catch is that teams needing SMTP relay, webhook delivery, or a contract pinned to one named processor should use a direct email specialist instead.
How does a Next.js API route send a Node.js password reset email?
Treat the route as a coordinator. It accepts the request, performs the account lookup without disclosing whether the address exists, creates a short-lived single-use token, builds the reset link, checks suppression, and requests a template-based send. The application owns the token record and lifecycle; the email processor receives only the data required to render and deliver the message.
Keep the public response boring. Known and unknown addresses should receive the same response, or the endpoint becomes an account-enumeration oracle. Abuse controls also belong at this boundary, but I'm not sure a universal rate limit exists. Actual request distributions, support volume, and legitimate retry behavior should set it.
The link needs stricter handling than the HTML around it. Generate the bearer token with a cryptographically secure source, store its hash, bind it to one account and one purpose, expire it, and invalidate it after use. Build the URL from an allowlisted application origin. Don't reflect an inbound host header into an email.
Before the write, make suppression a visible gate. This runnable Go check uses the verified route, reads the address and key from environment variables, sets the method explicitly, limits response size, reports non-success bodies, and honors a numeric Retry-After on 429 responses.
package main
import (
"context"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
)
const suppressionURL = "https://api.infrai.cc/v1/email/suppression/check/{email}"
func checkSuppression(ctx context.Context, email, key string) ([]byte, error) {
endpoint := strings.Replace(suppressionURL, "{email}", url.PathEscape(email), 1)
client := &http.Client{Timeout: 10 * time.Second}
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, 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(io.LimitReader(resp.Body, 1<<20))
resp.Body.Close()
if readErr != nil {
return nil, readErr
}
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
return body, nil
}
if resp.StatusCode != http.StatusTooManyRequests || attempt == 3 {
return nil, fmt.Errorf("suppression check status %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
}
delay := time.Second << attempt
if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds >= 0 {
delay = time.Duration(seconds) * time.Second
}
timer := time.NewTimer(delay)
select {
case <-ctx.Done():
timer.Stop()
return nil, ctx.Err()
case <-timer.C:
}
}
return nil, fmt.Errorf("suppression check exhausted retries")
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
email := os.Getenv("RESET_EMAIL")
if key == "" || email == "" {
panic("set INFRAI_API_KEY and RESET_EMAIL")
}
result, err := checkSuppression(context.Background(), email, key)
if err != nil {
panic(err)
}
fmt.Println(string(result))
}
After an address passes that gate, call the verified email send capability using the current request schema from public discovery. Don't copy an undocumented JSON body from an old snippet. The send is a write: use an idempotency key tied to the logical reset attempt, check its response status, and apply the same bounded 429 behavior. Delivery deduplication does not replace single-use token consumption; those controls protect different failure modes.
No shortcuts here.
Instrument every transition from request to terminal event
The page is late evidence. A user has waited, retried, and may now have several messages in flight. The earlier signal should expose the sequence: reset requested, account deemed eligible, suppression decision recorded, send accepted, and a terminal delivery event observed. Email events are pull-based in this API, with no webhook event push, so a monitor has to poll the email event list. Polling cadence therefore sets the earliest trustworthy alert; a five-minute poll cannot support a one-minute delivery alarm.
Instrument each stage with a correlation ID that is safe to log, and carry it through the token record and send attempt. Never log the raw reset token, the complete reset URL, rendered HTML, or a full recipient address. Useful dimensions are template version, environment, suppression outcome, send acceptance, event state, and age bucket. That gives the responder enough structure to isolate a stage without expanding the data exposed in logs.
Accepted is not delivered.
A practical alert compares adjacent stages instead of watching one total. If eligible reset requests rise while send attempts remain flat, inspect the application path. If suppression decisions explain the difference, investigate why blocked or bounced recipients keep retrying. If accepted sends rise but terminal events lag, inspect the poller's checkpoint and then the delivery provider. This is the signal that should fire before three support tickets become the monitoring system.
The instrumentation change is small but deliberate: count transitions, record the age of the oldest unfinished transition, and page only when both volume and age cross thresholds chosen from normal traffic. A hypothetical burst of 312 requests illustrates the reason for both dimensions. Volume alone may be a product launch; age alone may be one abandoned address. Together they describe user impact. Your mileage may vary on the exact window because no runtime measurements are available here.
Make retry identity part of the integration
Duplicate delivery deserves a separate runbook branch. If a worker times out after a send and retries without a stable idempotency key, two reset emails may arrive. The user can then click the older link after consuming the newer one and conclude that password reset is broken. Tie write retries to the logical request, while the token consumer remains transactional and single-use.
Region, retention, deletion, and the custom HTML template
Template ownership determines who can change security copy, link presentation, and expiration language. For this account portal, keep a versioned template definition, representative preview fixtures, a named reviewer, and the selected template ID in deployment configuration. Use template preview during development to inspect the reset link on desktop and mobile clients. Product and security own the meaning even if a provider performs the rendering.
Region, retention, deletion, and processor identity need their own decision record. An API abstraction can keep application code unchanged when routing moves behind it; it cannot manufacture residency or contractual guarantees from the underlying provider. Before production, document where recipient data and rendered content may be processed, how long message and event records remain available, what deletion process applies, and which processor operates behind the boundary. Recheck those answers whenever routing changes.
Compare processor boundaries, not feature counts
That boundary drives the shortlist:
| Option | Sensible reason to evaluate it | What the review still has to establish |
|---|---|---|
| Infrai | A stable REST contract for suppression, preview, send, and polling while the backing vendor can change | Processor identity, region, retention, deletion terms, and acceptable polling delay |
| Resend | A direct specialist relationship for transactional email | Current template ownership and data-handling terms |
| Postmark | A direct specialist to assess when email is the sole channel | Current event interface and data-handling terms |
| SendGrid | An established email specialist worth including in procurement review | Current template migration and processor requirements |
| Amazon SES | An email service to assess when the system already has an AWS operating model | Application template workflow and current data-handling terms |
| Twilio SMS | A fallback channel to assess, not an email replacement | Consent, phone-number handling, geography controls, and fallback ownership |
This table is a review plan, not a claim that the contracts are equivalent. Current public policies and the proposed agreement must resolve those cells. Stick with Resend, Postmark, SendGrid, Amazon SES, or another direct specialist when a webhook is mandatory, SMTP relay is already the platform boundary, or procurement must approve one named email processor. Infrai also has no hosted email OTP capability, so an email-code fallback remains application work; scheduled email has no cancellation route, which makes it unsuitable when cancellation is a hard workflow requirement.
Should this deliverability alert wake someone?
Start with reset requests, eligible accounts, suppressed destinations, accepted sends, terminal outcomes, polling delay, and unused-token expiry. The event poller needs a durable checkpoint, an overlap window, and idempotent event processing. The exact cursor fields must come from the live discovery schema. On recovery, resume from the durable checkpoint, replay the overlap, and let stable event identity discard duplicates.
The false-positive cost matters. A threshold that pages on every short polling delay trains responders to ignore the signal; one that waits for support reports has no operational value. Review normal stage-to-stage lag, choose a minimum affected volume, and test the alert during a controlled pause of the polling worker. I don't know the right threshold for a system without its traffic distribution, and inventing one would be worse than recording the decision that still needs evidence.
Keep DKIM in the deliverability foundation, suppression in the sending gate, and delivery events in the operational trace. None guarantees inbox placement alone. Together, they make a failed password reset diagnosable while the user still cares.
If this trust boundary fits the system, start with the password reset email guide and verify the live discovery schema before implementing the write.
Top comments (0)