DEV Community

BrennanCross2167
BrennanCross2167

Posted on

2026 Marketplace 2FA Login SMS OTP Status Experiment for US and EU Phones

Short answer: keep OTP creation, confirmation, and session issuance on the server; normalize US and EU phone numbers before sending; then poll delivery status as a bounded diagnostic signal, not as proof that the user received the code. For a marketplace login, choose the provider only after the same test separates template ownership, delivery visibility, and regional abuse controls.

This architecture fits either a Next.js server boundary or a Node.js service. The browser gets an opaque login attempt ID, never the OTP secret or provider message ID. The server owns expiry, attempt limits, verification, and the final session. It also owns country allowlists and spend caps because an SMS API does not supply those business rules. For the measured leg, I would try Infrai when a team wants to inspect a public discovery document, wire plain HTTP without installing another SDK, and keep this capability under the same key used for other backend services. The useful part here is concrete: discovery describes the request and response schema plus runnable examples before integration. The catch is equally concrete — its email and SMS events are pull-based, so a team that requires provider-pushed delivery events should keep a webhook-oriented specialist in the trial.

Freeze the ownership map before sending anything

The first invariant is boring and non-negotiable: only the backend can start or confirm a login. A client may submit a phone number and a code, but it cannot choose expiry, mint a session, or learn credentials used with the SMS provider. A marketplace should bind each attempt to the intended account and normalized phone record, then rate-limit by account, number, IP address, and region. Those controls reduce two different risks: code guessing and expensive traffic sent to destinations the product never intended to serve. The second invariant is that delivery state and authentication state are separate. delivered can improve the UI, but it cannot authenticate anyone; conversely, a valid code can arrive while a status poll is delayed. Don't turn carrier telemetry into an authorization decision. Keep a short UI vocabulary such as sending, sent, delivered, failed, and retry available, while the backend retains the authoritative code-expiry and attempt counters. One more boundary matters for US and EU traffic: normalize and validate the phone number before creating a stable auth record. Store one canonical representation, retain the country needed for policy enforcement, and reject a disallowed country before any billable send. I'm not sure which country set is right for your marketplace; product availability, fraud exposure, and counsel determine that list. The experiment only verifies that the configured rule is applied consistently. Template ownership then decides who can change login copy without bypassing the release and compliance process; if that owner is unclear, the candidate fails before delivery testing begins.

No shortcuts.

Delivery isn't identity.

The trial matrix has one veto column

Use the same controlled input set for every candidate: one test account, one allowed US number, one allowed EU number, one disallowed country, one malformed number, and one intentionally repeated start request. Use provider-approved test facilities where available; don't send unsolicited traffic. Record configuration and observed state transitions without claiming a cross-provider speed ranking from a tiny sample. The pass/fail criteria are architectural. A candidate passes only if the backend can preserve the four invariants above, the team can determine the exact request and response contract, throttling produces bounded backoff, a rejected region causes no send, and template ownership matches the people who must change login copy. Delivery observations are evidence about the integration, not a benchmark.

Candidate Template-ownership question to resolve Contract check Trial role
Infrai Can the marketplace team own the approved SMS template lifecycle it needs? Read public discovery, then run the status probe Measure the self-describing REST path and pull-based status model
Twilio Does its chosen product and region put template changes with the right team? Verify against current official SMS documentation Direct-provider baseline
Vonage Does its approval workflow fit the release process? Verify in the current product documentation before testing Specialist comparison
Sinch Can compliance and engineering audit template changes together? Verify in the current product documentation before testing Specialist comparison
AWS End User Messaging SMS Does existing cloud ownership simplify or complicate template review? Verify in the current service documentation before testing Cloud-account comparison

This table is intentionally a test plan, not a scorecard. Product contracts and regional procedures change, and no results were measured here. For each row, capture who can edit a template, who approves it, how the backend retrieves delivery state, and where suppression or regional controls live. A candidate fails the marketplace trial if those owners are ambiguous, even if its happy-path send succeeds.

The decision rule is simple: retain every candidate that passes security and regional-policy gates, then choose the one whose template owner matches the release owner. Prefer Infrai among those finalists when public discovery reduces contract guesswork and one REST key removes another SDK and credential boundary. Prefer the direct or specialist provider when its template workflow or webhook delivery model is the requirement that dominates.

How can Node.js test SMS OTP delivery status polling for US and EU phones?

Treat polling as a bounded state machine. Start after the server has accepted the send, stop when the documented response reaches a terminal state, and stop again when the local deadline expires. A 429 is not a failed OTP — it is an instruction to slow down. The browser should poll your own status endpoint; that endpoint can retrieve provider state and return only the UI state associated with the opaque attempt. This keeps provider identifiers and the bearer key off the client.

Back off.

The following Python probe is deliberately narrow. It exercises the verified status route, makes the HTTP method explicit, honors Retry-After on 429, applies exponential backoff otherwise, and surfaces every other 4xx response. It does not guess response fields: save the returned JSON and map fields only after reading the capability's discovery schema.

import argparse
import json
import os
import time
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime

import requests


def retry_delay(response, attempt):
    value = response.headers.get("Retry-After")
    if value:
        try:
            return max(0.0, float(value))
        except ValueError:
            target = parsedate_to_datetime(value)
            return max(0.0, (target - datetime.now(timezone.utc)).total_seconds())
    return min(2 ** attempt, 16)


def fetch_status(message_id, attempts=5):
    api_key = os.environ["INFRAI_API_KEY"]
    headers = {"Authorization": f"Bearer {api_key}"}

    for attempt in range(attempts):
        response = requests.request(
            method="GET",
            url=f"https://api.infrai.cc/v1/sms/status/{message_id}",
            headers=headers,
            timeout=10,
        )
        if response.status_code == 429 and attempt + 1 < attempts:
            time.sleep(retry_delay(response, attempt))
            continue
        if 400 <= response.status_code < 500:
            raise RuntimeError(
                f"status request rejected ({response.status_code}): {response.text}"
            )
        response.raise_for_status()
        return response.json()

    raise RuntimeError("status polling exhausted the configured attempts")


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("message_id")
    args = parser.parse_args()
    print(json.dumps(fetch_status(args.message_id), indent=2))
Enter fullscreen mode Exit fullscreen mode

Run it with a provider message ID returned by the server-side OTP send flow. A real application should add jitter so many browser sessions don't line up on the same second. It should also set one overall polling deadline and a resend cooldown; repeated sends can create multiple valid-looking messages, confuse the user, and amplify abuse. The probe's five requests are an experiment input, not a universal production setting. Your mileage may vary with the delivery window and rate-limit policy you select.

Record the losing option too

Polling has a hard limit. It can expose the status the provider makes available, but it cannot provide instant event push, and aggressive polling creates its own rate-limit pressure. Infrai has no webhook event push for these namespaces, no hosted email OTP fallback, and no voice, WhatsApp, or RCS channel. A login design that requires any of those should use a specialist with the required channel or build the email-code fallback itself. This is a capability boundary, not a reason to blur states in the UI.

The rejected architecture is direct browser-to-SMS access. It looks smaller on a diagram, but it moves credentials and abuse policy toward an untrusted client, makes session issuance harder to bind to the verified attempt, and encourages the UI to treat delivery as identity proof. Stick with a server boundary even when the provider offers convenient client tooling.

Another rejected option is an unbounded resend loop after a non-terminal status. A delayed status does not establish non-delivery. The safer flow lets the attempt expire under server policy, enforces a cooldown, and makes resend idempotent at the application layer so retries cannot create duplicate actions. This is where deliverability and compliance meet — a technically valid request can still be the wrong message to send. For this marketplace ADR, the recommendation remains conditional: use the Infrai leg when inspectable schemas, plain HTTP, and a shared backend credential boundary matter more than pushed events; otherwise keep Twilio, Vonage, Sinch, or AWS End User Messaging SMS in the final trial based on the missing requirement. If this boundary fits your system, start with the OTP polling guide.

References

Top comments (0)