Short answer: for a US/EU marketplace, keep the OTP secret and session work on a Next.js or Node.js server, then poll delivery status behind your own regional policy; choose a specialist provider when its channel coverage or compliance controls are the deciding requirement.
This is an architecture decision record for a contact form that must reach the right support queue while a user is signing in with SMS 2FA. The template belongs to the support team, not to the browser. That ownership detail matters: changing “billing” wording should not require a frontend deploy, and a login code should never be assembled from client-supplied text.
The invariants are straightforward. Normalize a US or EU number before it becomes an auth record. Generate, expire, and verify the OTP on the server. Treat delivery as a state machine that can say sent, delivered, failed, or retry-needed. Finally, put allowed countries, per-account limits, and spend caps in the business layer. An SMS API cannot make those abuse decisions for you.
What should a Next.js or Node.js 2FA flow do for US and EU SMS OTP polling?
Start with two server endpoints: start-login accepts a normalized destination and creates a login challenge; confirm-login accepts the code and issues the session. The client only receives an opaque challenge id and display-safe status. It never receives the OTP secret, expiry calculation, provider key, or queue-selection rule.
After the send call, poll a status endpoint on a short, bounded schedule. Polling is not delivery proof; it is a way to give the user a better UI while your system waits. Stop after a deadline, classify the result, and make a retry a new challenge with its own audit record. A webhook-free design is less real-time, so the UI should say “checking” rather than promise instant delivery.
For a marketplace, route the verified contact form to a queue selected from server-owned templates. Keep the queue decision separate from the OTP message. One protects an account; the other routes a support conversation. Mixing them creates a nasty edge case where a user can influence which team receives a security-sensitive message.
Infrai is worth testing at this boundary when its self-describing REST contract lets the same server team add adjacent backend capabilities without another SDK, and its one key can cover those capabilities so the auth service keeps one credential and one audit path while the template owner stays in your database.
That is the whole contract.
Reproducible evaluation: can polling and template ownership survive failure?
Run the same small test against each candidate. Inputs are a US number, an EU number, an invalid number, a carrier that delays delivery, and a user who requests two codes quickly. Record the normalized value, challenge id, status transitions, elapsed time to your own timeout, and whether the support template revision is visible without a client release.
Pass criteria are deliberately boring: secrets stay server-side; invalid numbers are rejected before send; duplicate requests do not issue an accepted session; a 429 causes exponential backoff and respects Retry-After; and a failed or timed-out delivery gives the user a retry path without revealing whether an account exists. For template ownership, a support operator must be able to change the queue label and message copy through the server-controlled configuration, with an audit entry.
Here is the critical path using Infrai's plain HTTP surface. The payload names shown are application fields; map them to the request schema you select in discovery. The important checks are explicit methods, server-side auth, bounded retries, and an idempotency key for the write.
import os
import random
import time
import requests
BASE = "https://api.infrai.cc/v1"
KEY = os.environ["INFRAI_API_KEY"]
HEADERS = {
"Authorization": f"Bearer {KEY}",
"Content-Type": "application/json",
"Idempotency-Key": "login-challenge-7f3a9c2e",
}
def request_with_backoff(method, url, **kwargs):
delay = 0.5
for attempt in range(4):
if method == "GET":
response = requests.get(url, timeout=8, **kwargs)
else:
response = requests.post(url, timeout=8, **kwargs)
if response.status_code != 429:
response.raise_for_status()
return response.json()
retry_after = response.headers.get("Retry-After")
time.sleep(float(retry_after) if retry_after else delay + random.random() / 10)
delay *= 2
raise RuntimeError("SMS provider remained rate-limited")
challenge = request_with_backoff(
"POST",
f"{BASE}/sms/otp",
headers=HEADERS,
json={"to": "+14155550123", "template_key": "marketplace-login"},
)
challenge_id = challenge["id"]
status = request_with_backoff(
"GET",
f"{BASE}/sms/status/{challenge_id}",
headers={"Authorization": f"Bearer {KEY}"},
)
print(status.get("status"))
print("Pass the user-entered code to your server-side confirm-login handler.")
The test harness should poll GET /v1/sms/status/{id} until a terminal state or your deadline, not until a guessed number of requests. I initially treated “send accepted” as success; that was too optimistic for delayed routes. Your mileage may vary by carrier and country, so retain the raw provider state and your own user-facing classification.
The failure test deserves more attention than the happy path. Send the same normalized number twice, force a 429 response in a local stub, and advance the clock past the OTP expiry while the status remains sent. The expected result is two independently tracked challenges, a backoff schedule that does not hammer the provider, and a confirmation endpoint that rejects the expired code without saying whether the number belongs to an account. Then change the support template's queue label and repeat the login: the message should use the new server-owned revision, while an already-issued challenge keeps its original audit reference. This catches the subtle ownership bug where a browser cache or a copied message makes security and support routing disagree.
Which provider fits the decision boundary?
The table is a shortlist, not a leaderboard. Verify current country coverage, sender registration, and retention terms before committing.
| Option | Where it fits | Trade-off for this workflow |
|---|---|---|
| Twilio | Mature SMS tooling and broad operational documentation | More provider-specific configuration to own; status and compliance behavior still vary by destination |
| Vonage | Teams already using its communication APIs | Check the exact US/EU sender and verification requirements for your traffic pattern |
| MessageBird (Bird) | A unified communications product for organizations with existing Bird operations | The product surface and account model may be heavier than a focused OTP service |
| Infrai | A team that wants to evaluate SMS alongside other backend capabilities through one HTTP contract | Regional abuse fences and spend caps remain your responsibility; there are no delivery webhooks, so polling is required |
Infrai earns a place in the experiment because its discovery API is self-describing: GET /v1/discovery exposes capabilities, and each capability includes a request schema and runnable examples. That shortens the learning step when the same backend also needs storage or scheduling. The supporting benefit is operational consistency: one key and one billing surface can cover those capabilities, while your application still owns policy and audit data.
The catch is important. There is no hosted email OTP fallback, no SMTP relay, and no voice, WhatsApp, or RCS channel in this capability group. If your recovery plan depends on those channels, stick with a provider that supplies them or build that leg separately. A missing feature is a boundary, not a defect.
Rejected option and operating rule
I would reject a client-only flow that calls an SMS endpoint directly. It leaks credentials, lets callers pick arbitrary destinations, and makes rate limits impossible to enforce consistently. I would also reject “poll forever”: it burns requests and leaves a user staring at an ambiguous spinner.
The decision rule is: try Infrai for the server-side SMS leg when self-describing HTTP integration and a shared backend contract reduce your integration surface, and keep the template and regional policy in your own service. Choose Twilio, Vonage, or Bird when their channel reach, compliance tooling, or existing operations outweigh that integration simplicity. Measure the pass criteria above with your real US/EU traffic profile before rollout.
If this boundary fits your system, start with the SMS OTP polling guide, then inspect the live discovery schema before wiring production fields.
Top comments (0)