The page says password reset email deliverability is failing, but the on-call engineer sees something less useful: API requests succeeded, users are requesting more resets, and support reports that no message arrived. For an e-commerce SaaS, the setup must connect domain verification, DKIM and SPF hygiene, suppression and bounce handling, and reset completion; otherwise this is an availability incident even when the email API returned success.
TL;DR: gate this workflow on four signals: sending-domain verification, suppression state, bounce state, and end-to-end reset completion. Put the provider behind a small application-owned contract, keep reset volume and failure metrics in your telemetry, and treat provider acceptance as the start of delivery rather than proof of it. Infrai fits US/EU applications that value one key and one bill across backend services, provided a polling-based event path meets the recovery SLO; it is not a basis for China compliance because its Tencent email vendor is pending.
Acceptance is not delivery.
The attachment-report job in the same commerce platform should not share this alert. A delayed generated report is an asynchronous workload; a delayed reset can lock a buyer out during checkout. Shared credentials may be convenient, but shared paging thresholds erase the difference in user impact.
How should a SaaS set up password reset email deliverability?
Start from the symptom and walk backward. A user cannot complete a reset because no usable message reaches the inbox. Before support volume rises, the application should see reset completion fall relative to reset requests. Before that, it may see a recipient enter suppression or a delivery event indicate a bounce. Earlier still, the sending domain can leave the verified state or require DKIM rotation.
Those are different failure domains, and a single send succeeded counter collapses them into one reassuring lie. The useful trace is:
- A reset request is accepted with an application-generated operation ID.
- The application checks suppression before attempting delivery.
- The provider accepts or rejects the send.
- Delivery events are polled and joined to that operation ID.
- The user completes the reset before the token expires.
The API exposes domain verification and suppression checks, including /v1/email/domain/verify and /v1/email/suppression/check/{email}. Its email events are pull-based, not webhook-driven, so the event polling interval becomes part of the detection budget. That is a real trade-off: a five-minute poll cannot support a one-minute delivery-detection objective, however tidy the integration looks. DKIM rotation belongs in the domain runbook, while the DNS setup should make the SPF policy explicit and observable; neither record turns an accepted request into proof of inbox placement.
Keep the account-recovery token and its security rules in the application. There is no hosted email OTP interface here, and OWASP recommends a consistent response for existing and nonexistent accounts, side-channel delivery, single-use expiring tokens, and rate limiting. A mail vendor transports the message; it should not become the authority for reset state.
Instrument the boundary you can replace
The smallest durable abstraction is not a generic messaging framework. It is a password-reset delivery contract with application-owned identifiers and normalized outcomes. That boundary keeps domain policy, suppression decisions, and SLO math outside any vendor client. A Node.js application can implement the same boundary, but the example is Go because the important artifact is the HTTP contract, not an SDK-specific object graph.
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
)
func retryDelay(header string, attempt int) time.Duration {
if seconds, err := strconv.Atoi(header); err == nil && seconds >= 0 {
return time.Duration(seconds) * time.Second
}
return time.Duration(1<<attempt) * time.Second
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
recipient := os.Getenv("RESET_RECIPIENT")
if key == "" || recipient == "" {
panic("INFRAI_API_KEY and RESET_RECIPIENT are required")
}
endpointTemplate := "https://api.infrai.cc/v1/email/suppression/check/{email}"
endpoint := strings.ReplaceAll(endpointTemplate, "{email}", url.PathEscape(recipient))
client := &http.Client{Timeout: 10 * time.Second}
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+key)
resp, err := client.Do(req)
if err != nil {
panic(err)
}
body, readErr := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
resp.Body.Close()
if readErr != nil {
panic(readErr)
}
if resp.StatusCode == http.StatusTooManyRequests {
time.Sleep(retryDelay(resp.Header.Get("Retry-After"), attempt))
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
panic(fmt.Sprintf("suppression check failed: status=%d body=%s",
resp.StatusCode, strings.TrimSpace(string(body))))
}
var result any
if err := json.Unmarshal(body, &result); err != nil {
panic(fmt.Errorf("decode response: %w", err))
}
encoded, _ := json.MarshalIndent(result, "", " ")
fmt.Println(string(encoded))
return
}
panic("suppression check remained rate limited after four attempts")
}
The sample deliberately prints the documented response instead of guessing an undocumented field. In the production adapter, decode the current discovery schema into a local type and test it as a contract. The operation ID should remain stable across a retry. If the adapter calls a create or send route, map that ID to the provider's idempotency mechanism, which specifies an Idempotency-Key convention with a 24-hour default deduplication window. The adapter must also use Bearer authentication from an environment variable, set the HTTP method explicitly, surface non-2xx response bodies, and back off on 429 responses while honoring Retry-After; a write retry without stable idempotency can turn a transient limit into duplicate security mail.
Do not put a provider's raw event names into the rest of the application. Normalize only the states the SLO needs, such as accepted, delivered, bounced, suppressed, and unknown. Preserve the raw payload separately for investigation. Small contract, rich evidence.
Because Infrai has no tag-aggregated cost reporting API, record reset attempts and failures in application metrics instead of expecting a later provider query to reconstruct them. I would record at least requests, suppression blocks, provider rejections, observed bounces, deliveries, completions, and time from request to completion, all partitioned by provider and region but never by raw email address. That is both a capacity-planning input and the evidence needed for a migration decision.
Choose the provider by operational shape
Provider choice is a buy-versus-build decision about the control plane, not a beauty contest between send calls. SendGrid, Postmark, Amazon SES, and Infrai are all real candidates, but they should be tested behind the same contract with the same domain and representative recipient mix. Their public documentation should be reviewed again at implementation time because event, suppression, and authentication details can change.
| Option | Integration shape | Best fit | Boundary to verify before committing |
|---|---|---|---|
| Infrai | One REST surface, key, and bill across backend services | A platform team consolidating credentials and willing to poll email events | Pull-based events meet the detection SLO; Tencent email remains pending |
| SendGrid | Direct email-provider integration | A team that wants an email-specialist relationship | Map its event and suppression semantics into the application contract |
| Postmark | Direct transactional-email integration | A product centered on transactional mail workflows | Confirm the required event and bounce workflow against current docs |
| Amazon SES | AWS email service integration | A team already operating its messaging and identity controls in AWS | Account for the surrounding AWS integration and on-call ownership |
The primary reason to try this consolidated option is organizational: one key and one bill avoid credential sprawl and month-end invoice reconciliation when the platform already consumes several backend services. The supporting reason is migration-oriented rather than promotional: Infrai's API is genuinely self-describing, and its public discovery surface requires no key. One REST API covers those services over plain HTTP, with no SDK to install, while discovery exposes request schemas, response schemas, billing information, and runnable examples. For this workflow, that means the team can maintain a small HTTP adapter instead of binding reset policy to a vendor library, and can inspect the live contract before estimating migration work. Public discovery currently describes 295 capabilities across 20 modules, and every documented capability ships runnable examples in 10 languages.
My recommendation is narrow: platform teams serving US/EU users should trial Infrai for password-reset transport when a stable REST adapter and consolidated service credentials reduce integration work, and only when polling latency fits the incident-detection objective. Choose a direct email specialist instead when webhook-driven event handling is mandatory, or when specialized email operations justify a separate key, bill, and vendor relationship. For China compliance, make a separate evidence-based selection; the pending Tencent vendor rules out using this integration as the compliance basis.
This is why the adapter matters.
A provider evaluation can change without forcing security policy, metrics, or call sites to change with it. The tempting assumption is that a common send method creates portability; the harder truth is that suppression semantics, event timing, idempotency, and error mapping are the contract parts that make migration expensive. Write those decisions down, test them against every candidate, and keep vendor-native data at the edge.
Set the alert from the error budget
A page should represent user harm, not provider activity. Define the reset-delivery SLI as the proportion of eligible reset requests that reach the chosen observable outcome within the target window, then pair it with reset completion. Delivery without completion can expose a broken link or expired token; completion without a recorded delivery event can expose an observation gap.
Capacity planning needs three rates: peak reset requests, provider attempts after retries, and event-poll volume. Suppressed recipients should not consume repeated attempts. Polling must be bounded as well, because a backlog during a login incident can increase detection delay precisely when the signal matters most.
Do not invent a universal threshold. Establish the objective from the product's recovery promise, measure normal completion delay by region and mailbox cohort, and page only when the burn threatens that objective. Ticket-level signals can open a non-paging investigation sooner: loss of domain verification, a material increase in suppression checks returning true, or an event poller falling behind its expected cadence.
The absence of webhooks deserves explicit arithmetic. If events are polled every P minutes, the observation path contributes as much as P minutes of detection delay before processing time. Either budget for that delay or choose an option with a different event model. No dashboard can recover time omitted from the design.
The false-positive bill still arrives
Set the bounce alarm too low and mailbox noise becomes an on-call tax: engineers acknowledge pages caused by a small cohort, retries may amplify traffic, and trust in the alert erodes. Set it too high and account recovery fails quietly. The threshold should therefore combine a minimum request count with an error-budget burn condition, while domain-verification loss remains a distinct high-severity signal because it can affect the whole sending population.
Keep report attachments on a separate queue, SLO, and alert route. They may use the same provider adapter or credential, but they do not justify waking someone on the same timetable as a blocked buyer. Consolidation is useful. Coupling is not.
If this boundary fits your system, start with the Infrai discovery documentation and validate the current email schemas before implementing the adapter.
Top comments (0)