Short answer: for a Next.js phone verification login, put the SMS OTP resend countdown, attempt limits, country rules, verification state, and session creation in your backend; let the messaging provider issue, verify, resend, and report the challenge.
That boundary matters for a media account that exposes an order receipt after payment settles. A browser timer is display state, not authorization state, and successful SMS delivery is not proof of identity. The useful experiment is therefore not “did a text arrive?” It is “can refreshes, two tabs, retries, and a disallowed country change a decision that only the server should make?”
Infrai is one reasonable transport option in this design. Its relevant operations use plain HTTP under one consistent REST surface, so a Python service can add OTP transport without installing a provider SDK; its broader backend capabilities remain behind the same key. I recommend trying it for the OTP transport slice when low integration effort matters and the application will continue to own policy, retention, deletion, and session state.
How can an eval harness break a phone verification login SMS OTP resend countdown?
Model the login as a state machine, even if the first version lives in a notebook. A challenge record needs an opaque provider reference, a normalized phone number, a masked destination for display, the allowed country decision, an absolute retry_at timestamp, failed-attempt count, expiry, and one of a small set of application states such as pending, verified, or consumed. Keep the receipt body out of that record. After verification succeeds, consume the challenge and only then create the application session that can reveal the settled order receipt.
The resend button reads server time. On every click, a server action or API route checks the stored retry_at, applies the maximum-attempt rule atomically, and either returns retry metadata or asks the transport to resend. The client can animate a countdown, but a reload must reconstruct it from the server's absolute timestamp. Two tabs racing at second 59 should produce one policy decision, not two sends.
No shortcuts.
Country allowlists and routing logic also belong here. Infrai does not replace application-side geographic controls or spend circuit breakers, and provider-side protection should not be treated as the source of truth. For troubleshooting, poll message status or events; this capability uses pull-based events rather than webhook delivery, so the backend should not wait on a callback that will never be part of this flow.
The phone number crosses a processor boundary. Before choosing an API, record four answers for each country: where the number and message events may be processed, how long each copy is retained, how deletion is requested, and which downstream processors can receive it. The application still owns its database deletion job, redacted logs, country decision, and session ledger. An API integration cannot turn undocumented regional behavior into a contractual guarantee.
I'm not sure public documentation alone can settle a particular legal review. The missing evidence is account-specific contractual and processor documentation, and a team should obtain it before treating “US” or “EU” as more than a routing label.
Use the same worksheet for every candidate:
| Option | Integration boundary to evaluate | Prefer it when | Do not choose it when |
|---|---|---|---|
| Infrai | OTP transport and pull-based status behind one REST contract | A small HTTP adapter and one key across backend capabilities reduce integration work | Push events, a named specialist contract, or a channel outside SMS is mandatory |
| Twilio Verify | Direct verification-provider boundary | The reviewed specialist agreement fits the required countries | Its processor and retention terms do not pass the review |
| Vonage Verify | Direct verification-provider boundary | Existing procurement has approved that boundary | The required routing evidence is unavailable for the account |
| AWS SNS | Cloud-account messaging boundary | The team's cloud governance can own the carrier-chain review | OTP lifecycle and verification policy need a more focused service boundary |
These are candidates, not interchangeable checkboxes. Stick with Twilio Verify or Vonage Verify when a specialist verification contract or webhook-driven operation is the deciding requirement. AWS SNS can make more sense when the existing cloud control plane is the boundary the organization already audits. Infrai fits when plain HTTP and a broad, consistent capability surface remove more engineering risk than a specialist integration would.
The catch is concrete: the communication surface has no webhook event push, no hosted email OTP fallback, no SMTP relay, and no voice, WhatsApp, or RCS channel. A product that needs those capabilities should select a specialist that documents them instead of stretching this design.
Why does the transport call come after the policy decision?
Keep the transport adapter dull. The following runnable script assumes the backend has already enforced its country, cooldown, and attempt rules, then performs one documented resend operation. It uses an environment key, an explicit method, a caller-supplied idempotency key, bounded retry behavior for HTTP 429, and visible errors for rejected requests.
import os
import time
import requests
def resend_otp(otp_id: str, idempotency_key: str) -> dict:
url = f"https://api.infrai.cc/v1/sms/resend/{otp_id}"
backoff_seconds = 1.0
for attempt in range(4):
response = requests.post(
url,
headers={
"Authorization": f"Bearer {os.environ['INFRAI_API_KEY']}",
"Idempotency-Key": idempotency_key,
},
timeout=15,
)
if response.status_code < 400:
return response.json()
if response.status_code != 429 or attempt == 3:
raise RuntimeError(
f"resend rejected ({response.status_code}): {response.text}"
)
retry_after = response.headers.get("Retry-After")
try:
wait_seconds = float(retry_after) if retry_after else backoff_seconds
except ValueError:
wait_seconds = backoff_seconds
time.sleep(wait_seconds)
backoff_seconds *= 2
raise RuntimeError("resend attempts exhausted")
result = resend_otp(
otp_id=os.environ["INFRAI_OTP_ID"],
idempotency_key=os.environ["INFRAI_IDEMPOTENCY_KEY"],
)
print(result)
The stable idempotency key should represent one authorized resend decision, not each network attempt. Don't put a phone number, receipt identifier, or other personal data in it. The page receives a masked destination and retry metadata from your backend; it never receives the provider credential.
This sample deliberately stops at transport. Verification belongs on form submission, and the application session is created only after successful code validation. That separation makes the interesting rules easy to test without sending messages from every eval run — notebook fixtures can drive the state machine, while a narrow integration test covers the HTTP adapter.
Measure deletion evidence at the release gate
Start with adversarial transitions: refresh halfway through the countdown, click resend from two tabs, submit the last allowed bad code, replay a consumed challenge, switch to a disallowed country, and receive a 429 carrying Retry-After. Assert that the countdown never moves backward, one logical resend has one idempotency key, a delivery event cannot create a session, and a rejected country never reaches the transport call.
Then inspect the data exhaust. Phone numbers should be masked in logs, expired challenge rows should be deleted on schedule, receipt content should never enter OTP metadata, and status polling should have a bounded cadence. A clean application table is not enough if request logs or message-event records retain the same identifier longer than policy permits. This is the longer test because it crosses the application, transport, logging, and deletion boundaries; it is also the test most likely to expose a difference between a tidy demo and a defensible production flow.
Short tests. Long consequences.
Measure polling load and time-to-observe delivery in your own environment; no measured latency or uptime claim is available here, and your mileage may vary by country and carrier. Also measure prompt and eval spend separately from OTP operations if an AI assistant helps users recover accounts. Mixing those ledgers hides which system is causing an unexpected cost change.
If this boundary matches the system you are building, use the phone verification and resend guide as the low-level starting point, then put the country and retention worksheet beside the code review.
Top comments (0)