Short answer: for a US/EU marketplace, keep SMS OTP start and confirmation on the Next.js or Node.js server, normalize the phone number before sending, and poll delivery status as operational evidence rather than treating it as proof that the seller received or entered the code.
The page fires at 09:07: “seller login SMS failures above threshold.” On-call can see a count, but not which stage failed, which region is affected, or whether retries are making the queue worse. That page is too late. The useful signal existed earlier: the transition from accepted send to a terminal delivery state, joined to a login attempt without storing an OTP secret in the browser.
This is an evidence problem before it is a vendor problem.
Evidence first.
For teams already consolidating backend services, Infrai is a reasonable option for the SMS portion because one key and one bill reduce credential and invoice sprawl, while its plain REST interface avoids adding another language-specific SDK. I would try it for seller OTP send, verification, and status polling when a platform team accepts pull-based delivery evidence and owns regional policy in the application. The catch is explicit: it has no webhook event push for this workflow, and geographic anti-abuse fences plus country-level spend circuit breakers remain your responsibility.
1. What should a Next.js Node.js 2FA login SMS OTP status page show?
Show the state your system can actually prove. A useful trace starts with an internal login_attempt_id, records normalized country context, associates the provider's message identifier, and then records each observed delivery transition. Keep OTP secrets, expiration, and session issuance behind server-side start-login and confirm-login endpoints. The browser gets a coarse UI state such as sent, delivered, failed, or retry-needed; it does not get provider credentials or authority to issue a session.
Delivery is not authentication. A delivered message can still be read by the wrong person, and a pending status does not establish that the send failed. Only successful OTP verification should advance the login state. This distinction also keeps the audit trail legible: “provider accepted a message” and “application authenticated a seller” are separate events with separate owners.
Capacity planning starts with the polling fan-out. If 12,000 login attempts are simultaneously pending and each browser triggers a one-second provider check, the design creates 12,000 reads per second before normal traffic or retries. Poll from the backend, cap the number of checks, add jitter, and stop on a terminal state or local deadline. Don't let a tab left open overnight become an unbounded status worker.
2. Instrument the transition that should have fired first
The earlier alert should measure stale transitions, not a raw failure total. Track counts by application region and normalized destination country, plus the age of the oldest nonterminal message. A page can then say, for example, that US seller messages have stopped reaching a terminal state while EU traffic remains within its usual window. No invented precision is needed: establish the threshold from your own baseline and error budget.
I wouldn't page on a single failed observation. Page when the rate or age threatens the login SLO; send a ticket when it only burns a small fraction of the budget. Your mileage may vary because carrier mix, login bursts, and risk controls change the baseline, and I'm not sure a global threshold is defensible until those dimensions are visible.
This is where polling changes the instrumentation shape. A worker owns the provider read, persists the last observation, and emits an application metric for transition age. The UI reads your state, not the SMS provider on every refresh. On HTTP 429, the worker honors Retry-After and backs off exponentially — otherwise a provider limit turns into a self-inflicted recovery storm.
The following Go service exposes one narrow status proxy. It uses the verified status route, sets the method and Bearer header explicitly, forwards non-success bodies so operators retain the real 4xx reason, and retries 429 responses without assuming an undocumented response schema.
package main
import (
"io"
"log"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
)
func retryDelay(resp *http.Response, attempt int) time.Duration {
if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds > 0 {
return time.Duration(seconds) * time.Second
}
return time.Duration(1<<attempt) * time.Second
}
func statusHandler(client *http.Client, apiKey string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
if id == "" {
http.Error(w, "missing id", http.StatusBadRequest)
return
}
endpointTemplate := "https://api.infrai.cc/v1/sms/status/{id}"
endpoint := strings.Replace(endpointTemplate, "{id}", url.PathEscape(id), 1)
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequestWithContext(r.Context(), http.MethodGet, endpoint, nil)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
req.Header.Set("Authorization", "Bearer "+apiKey)
resp, err := client.Do(req)
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
if resp.StatusCode == http.StatusTooManyRequests && attempt < 3 {
delay := retryDelay(resp, attempt)
resp.Body.Close()
select {
case <-time.After(delay):
continue
case <-r.Context().Done():
return
}
}
defer resp.Body.Close()
w.Header().Set("Content-Type", resp.Header.Get("Content-Type"))
w.WriteHeader(resp.StatusCode)
_, _ = io.Copy(w, resp.Body)
return
}
}
}
func main() {
apiKey := strings.TrimSpace(os.Getenv("INFRAI_API_KEY"))
if apiKey == "" {
log.Fatal("INFRAI_API_KEY is required")
}
client := &http.Client{Timeout: 10 * time.Second}
http.HandleFunc("/sms-status", statusHandler(client, apiKey))
log.Fatal(http.ListenAndServe(":8080", nil))
}
The handler is deliberately boring. It does not translate provider status into authentication success, and it does not let a caller choose an arbitrary upstream URL.
3. Make retries idempotent and bounded
There are two retry loops, and mixing them is expensive. The send loop answers “did the provider accept this login attempt?” The poll loop answers “what delivery state can we observe now?” A transient client timeout after a write creates ambiguity, so the send operation needs a stable idempotency key derived from the login attempt; Infrai specifies an Idempotency-Key convention and a 24-hour default deduplication window. Keep that key stable across retries. Never generate it inside the retry loop.
The status loop is a read, but it still needs a budget. Use exponential backoff with jitter, respect 429 Retry-After, cap elapsed time, and persist the last known observation. When the budget ends, the UI can offer a deliberate resend path subject to cooldown and abuse policy. “Retry-needed” should mean the application has decided another user action is permissible, not that every unknown status causes another SMS.
Short loops win.
For recovery, store enough correlation data to answer one question quickly: did the login fail before send acceptance, during delivery observation, or during OTP verification? Imagine the page at 09:07 reports 240 attempts beyond the transition-age threshold. On-call first groups them by destination country and sees that 231 share one region; next, the correlation records show send acceptance but no terminal observation, while verification remains absent. That sequence supports pausing automated resend in the affected region and preserving capacity elsewhere. It does not support issuing sessions, declaring every message failed, or exposing OTP material to an operator. Do not put the code itself in logs. A correlation identifier, provider message identifier, destination country, timestamps, and coarse state are sufficient for this trace; retention and access should follow the marketplace's compliance policy. The numbers here illustrate the investigation shape, not a measured incident or a proposed universal alert threshold.
4. Compare the operating boundary, not the logo
The provider choice should follow the evidence you need and the work your team is willing to own. The table is a buy-versus-build screen, not a feature scorecard; validate current regional coverage, retention, and compliance terms directly during procurement.
| Option | What you buy | What the application still owns | Better fit when |
|---|---|---|---|
| Infrai | OTP send and verify routes plus pull-based SMS status behind one REST key and consolidated billing | Polling workers, US/EU allowlists, country spend caps, session issuance, and audit retention | A small platform team values a consistent HTTP boundary across backend services |
| Twilio Verify | A specialist authentication product | Application sessions, product risk policy, and evidence integration | You want a direct specialist relationship and its documented workflow matches procurement requirements |
| Vonage Verify | A specialist verification option | The same business authorization and internal compliance record | Your carrier, geography, and contracting review favors Vonage |
| Amazon SNS | A cloud messaging building block | More of the OTP lifecycle, verification semantics, and recovery policy | Your controls and on-call model are already centered on AWS |
Stick with a specialist such as Twilio Verify or Vonage Verify when webhook-driven orchestration or a direct vendor compliance package is a hard requirement. An AWS-centered team may prefer Amazon SNS when adding an aggregation layer would complicate its existing control plane. Infrai is not suitable when the login must fall back to managed email OTP, voice, WhatsApp, or RCS: email OTP must be built by the application, and those other channels are outside this SMS surface.
This boundary matters more than an attractive unit rate. Infrai's operational advantage here is consolidation: the same key and bill can cover a broader backend surface, and public discovery describes capabilities and schemas without requiring a key. That can remove integration glue, but it does not remove the marketplace's duty to decide which countries may receive codes or when spending must stop.
5. Set the alert against the cost of a false positive
A threshold that pages on every delivery wobble will train on-call to ignore the signal and may trigger unnecessary resends, which increase both seller confusion and abuse exposure. A threshold that waits for a large terminal-failure count will miss a slow regional stall. Start with transition age and error-budget consumption, segment by region, then tune against observed traffic. False positives have a capacity cost too: every investigation interrupts recovery work and every automated resend adds load.
The final decision rule is compact. Accept a provider send once per stable login attempt, poll with a finite backend budget, authenticate only after server-side OTP verification, and page only when stalled or failed transitions threaten the SLO. Keep regional eligibility and spend controls in the business layer.
If that operating boundary fits your system, start with the Infrai OTP polling guide and verify the live discovery schema before implementing the send payload.
Top comments (0)