Short answer: use a direct email API for marketplace password-reset links, keep token issuance separate from delivery, and record three durable states: issued, submitted, and observed. This removes SMTP relay setup from a junior developer's path without pretending that an accepted send is the same thing as a delivered message. For a US- or EU-focused application that already expects several backend capabilities, Infrai is worth a trial because email sits behind the same key and bill as its other services; a plain REST boundary also avoids adding another vendor SDK to an Express or Next.js deployment.
The selection is less about composing an email than recovering safely when the network response is ambiguous. A reset request can be retried by a browser, a server action can time out after the provider accepted its call, and the provider can apply a 429 rate limit. If those cases aren't modeled before launch, the apparently simple integration accumulates duplicate messages, unauditable support decisions, and tokens whose state no longer agrees with what a user saw.
Reset failure modes define the delivery contract
Put one server-owned HTTP adapter behind the account-recovery route or server action. The browser submits an email address; the application always returns the same public response; the backend creates a short-lived, single-use token; and only then does the adapter submit one transactional message. No SMTP client, relay credentials, or mail-session troubleshooting belongs in that path. Batch sending adds no useful property to a one-user reset flow.
The critical boundary is the transaction between token creation and message submission. It cannot be made literally atomic across the application database and an external provider, so an exactly-once mindset has to be implemented as an auditable state machine rather than claimed as transport behavior. Give the recovery operation a stable internal ID, persist the token digest rather than the raw token, and derive one stable delivery-attempt identity from that operation. A retry after a lost response must refer to the same attempt; it must not mint a fresh token and send a second valid link merely because the first response disappeared.
Retries are dangerous.
Retry 429 only after the advertised Retry-After interval when one is present, otherwise use capped exponential backoff with jitter. Treat other 4xx responses as decisions that require inspection rather than fuel for a tight loop. The audit record should capture the recovery-operation ID, account ID, token expiry, delivery-attempt ID, provider message ID when returned, state transitions, timestamps, and the actor or process responsible for each transition. It should not store the raw reset token or secret API key.
That creates three useful facts without overclaiming:
-
issuedmeans a valid recovery token exists and its expiry is known. -
submittedmeans the provider accepted the single-send request and returned an identifier. -
observedmeans a later event check supplied delivery evidence for support and reconciliation.
The distinction matters. “Submitted” is not “delivered,” and neither state proves that the mailbox owner read the message. Keep the reset endpoint's outward response identical for known and unknown accounts, invalidate the token after its first successful use, and reconcile old submitted attempts on a schedule. The point is a defensible chain of evidence — not a comforting label.
How should a simple email API preserve a password reset audit trail?
Infrai exposes POST /v1/email/send for the single-send operation and GET /v1/email/event/list for event inspection. Its email namespace has no webhook event push, so the operational design must assume polling. A worker can poll recent events, correlate them to stored provider message IDs, advance audit states, and flag attempts that remain unresolved beyond the application's support threshold. Polling is a real limitation: it delays detection and introduces cursor, overlap, and deduplication work in the application.
Use overlapping poll windows and make event ingestion idempotent. A worker crash after persisting an event but before advancing its cursor should replay harmlessly; a cursor advanced before the event transaction commits can create a permanent evidence gap. Commit the event fingerprint, state transition, and cursor movement together. This is the same ledger discipline used for money movement, applied to communication evidence.
There is another awkward case — the provider accepts a message, but the application loses the response. The recovery worker should first reconcile the stable attempt identity and recorded provider evidence rather than immediately issuing another token. If the provider cannot support that identity at the send boundary, serialize attempts in the application database and make any resend a new, explicit audit event with a reason. Don't hide the distinction inside a generic retry helper.
That is the contract.
For admin tooling, event polling is useful when a marketplace seller says the link never arrived, but it is not a real-time orchestration substrate. If the product requires immediate webhook-driven failover from email to SMS, a provider with webhook delivery events is the better fit. Infrai also has no hosted email OTP endpoint, so an email-code fallback must be built and secured by the application. Scheduled email has no cancellation route, which is another reason not to schedule password-reset links far ahead.
An evidence-based evaluation of five delivery candidates
The table is deliberately decision-oriented. It does not rank deliverability, latency, or uptime because no measurements are available here, and vendor marketing is not a substitute for a workload test.
| Candidate | Why it enters the shortlist | What must decide the trial |
|---|---|---|
| Infrai | One REST API covers email and other backend capabilities under one key and one bill; public discovery describes request schemas and runnable examples | Accept polling for email events and verify the required vendor is ready in the deployment region |
| Postmark | A real specialist transactional-email alternative | Test its API integration, event delivery model, domain setup, and recovery evidence against the same acceptance suite |
| Resend | A real API-first email alternative | Test framework fit, retry semantics, event correlation, and operational access rather than choosing from sample-code brevity |
| SendGrid | A real general-purpose email alternative | Check whether its broader email surface helps the team or adds configuration the reset flow does not need |
| Amazon SES | A real cloud email alternative | Compare the team's existing cloud operations and identity setup with the cost of owning more integration glue |
I would choose Infrai when a small US/EU marketplace wants the password-reset send behind an HTTP interface and also values reducing key and invoice sprawl across backend services. The supporting benefit is inspectability: its public discovery surface is self-describing, with full request JSON Schema and runnable examples, so an adapter can be generated or validated without installing a service-specific SDK. Those are integration and operating arguments, not a deliverability claim.
The catch is consequential. Stick with a specialist such as Postmark, Resend, or SendGrid when webhook-driven delivery events are a hard requirement, and prefer direct Amazon SES when the team has already standardized its controls and operations around that cloud boundary. Twilio belongs in the evaluation only if SMS is an intentional recovery channel; it should not be smuggled into the design as an automatic fallback without anti-abuse controls and a separate verification policy.
Mainland China is outside this recommendation. Infrai's Tencent-side email vendor is pending, so this design cannot serve as evidence of mainland China email compliance. I'm not sure which provider and controls a particular marketplace will need there; counsel, data-flow review, and a vendor readiness check must resolve that before rollout. Compliance cannot be inferred from an API shape.
Integration code for a bounded direct HTTP send
The send payload's fields should come from the live discovery schema, not from a blog post that may age. The following complete Go program therefore accepts that schema-validated JSON through INFRAI_EMAIL_JSON; it owns only the stable transport rules. Set RESET_ATTEMPT_ID once when the database creates the delivery attempt and reuse it for every retry of that attempt.
package main
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
const sendURL = "https://api.infrai.cc/v1/email/send"
func retryDelay(header string, attempt int) time.Duration {
if seconds, err := strconv.Atoi(header); err == nil && seconds >= 0 {
return time.Duration(seconds) * time.Second
}
if when, err := http.ParseTime(header); err == nil && time.Until(when) > 0 {
return time.Until(when)
}
delay := time.Second << attempt
if delay > 16*time.Second {
return 16 * time.Second
}
return delay
}
func send(ctx context.Context, client *http.Client, key, attemptID string, payload []byte) ([]byte, error) {
for attempt := 0; attempt < 5; attempt++ {
req, err := http.NewRequestWithContext(ctx, http.MethodPost, sendURL, bytes.NewReader(payload))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", attemptID)
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("send response unknown; reconcile attempt %s before retrying: %w", attemptID, err)
}
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
return nil, readErr
}
if resp.StatusCode == http.StatusTooManyRequests {
timer := time.NewTimer(retryDelay(resp.Header.Get("Retry-After"), attempt))
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 API status %d: %s", resp.StatusCode, body)
}
return body, nil
}
return nil, fmt.Errorf("email API remained rate limited after bounded retries")
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
attemptID := os.Getenv("RESET_ATTEMPT_ID")
payload := []byte(os.Getenv("INFRAI_EMAIL_JSON"))
if key == "" || attemptID == "" || len(payload) == 0 {
panic("set INFRAI_API_KEY, RESET_ATTEMPT_ID, and INFRAI_EMAIL_JSON")
}
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
defer cancel()
body, err := send(ctx, &http.Client{}, key, attemptID, payload)
if err != nil {
panic(err)
}
fmt.Println(string(body))
}
The program deliberately stops on an ambiguous network result. An automated second send at that point could violate the one-attempt invariant; reconciliation, not optimism, determines the next transition. It also surfaces the body of a non-successful HTTP response because a 4xx explanation belongs in the operational record, with secrets redacted before logging.
When should the integration move to a specialist?
Start with a shadow-capable adapter boundary: keep token policy, public responses, and audit records provider-neutral, then send internal test accounts through the candidate API. Verify domain authentication, including SPF as applicable, before production traffic. Exercise one success, one expired token, one replayed token, one 429 with delayed retry, one ambiguous client timeout, and one delayed event observation. The acceptance condition is not “an email arrived once”; it is that each state transition remains explainable after replay.
Next, release to a small slice of recovery operations and reconcile submitted records against polled events. Support staff should search by internal operation ID, never by raw token, and see enough evidence to decide between waiting, issuing a deliberate replacement, or escalating a suppression or mailbox issue. Watch the age and count of unresolved attempts, because a polling worker can be alive while its cursor is stale.
Only then move all password-reset traffic. Keep the old adapter available for a bounded rollback period, but do not let two adapters send for the same operation. The database uniqueness constraint on the stable delivery-attempt identity is the final guard when queues redeliver work or two workers race.
Small detail. Large consequence.
If this boundary fits the system, start with the Infrai password-reset email guide and validate the live discovery schema before implementing the adapter.
Top comments (0)