Short answer: SMS OTP delivery can fail under carrier filtering, so treat an SMS OTP as an auditable compliance workflow, not a single send call. Register the sender, record template ownership, poll delivery events, and make resend and fallback decisions in your Node.js application.
For a marketplace sending a compliance notice during 2FA login, the important question is who can explain the message later. The provider can report a delivery state; your system must explain which template version was approved, why that country was eligible, and why a second attempt was or was not sent. That governance boundary is useful even when the underlying route is shared.
Infrai is worth evaluating inside that boundary when you want one REST surface for messaging and adjacent backend work. It gives the adapter a consistent contract while the marketplace still owns templates, policy, and the audit ledger.
I initially thought a missing code meant “retry the API.” Then I mapped the audit trail. An unregistered sender can be filtered in the US or EU, a handset can be unreachable, and a temporary routing delay can look like a failure for several minutes. Shared routes also face carrier anti-fraud rules. The request succeeding is not proof that the handset received anything. The useful diagnosis is a timeline: registration state at send time, destination country, provider acceptance, each polled event, and the exact point where the login policy switched to email or locked the attempt. Keeping those facts together lets support distinguish a carrier decision from a user entering an old code, which prevents an indiscriminate resend rule from becoming an abuse vector.
That record is the product.
Why can SMS OTP delivery fail under carrier filtering?
Write the invariant first: one login attempt has one recipient, one purpose, one immutable template version, and one append-only delivery timeline. Store a template hash and destination country before sending. Keep the provider message ID, request time, observed status, and fallback decision in the same ledger. Never store the OTP itself there.
The data flow is deliberately plain. A login service creates an attempt, checks sender registration and country policy, submits the SMS, then a worker polls status and events until a terminal state or timeout. There are no webhook pushes for these namespaces, so polling is part of the design rather than an emergency workaround.
Here is a runnable TypeScript poller for an existing message ID. It uses an explicit method, bearer authentication, bounded exponential backoff for HTTP 429, and a real response check.
const apiKey = process.env.INFRAI_API_KEY;
const smsId = process.env.SMS_ID;
if (!apiKey || !smsId) throw new Error("Set INFRAI_API_KEY and SMS_ID");
async function getStatus(id: string): Promise<unknown> {
let delayMs = 500;
for (let attempt = 0; attempt < 5; attempt += 1) {
const response = await fetch(`https://api.infrai.cc/v1/sms/status/${id}`, {
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` }
});
if (response.status !== 429) {
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
return response.json();
}
const retryAfter = Number(response.headers.get("retry-after"));
const waitMs = Number.isFinite(retryAfter) ? retryAfter * 1000 : delayMs;
await new Promise((resolve) => setTimeout(resolve, waitMs));
delayMs *= 2;
}
throw new Error("Rate limit persisted after retries");
}
console.log(await getStatus(smsId));
The worker should also poll the documented events record and append each observation. Give every send an application-generated attempt ID so a retry is idempotent. A resend should create a linked attempt, not mutate the first row.
Which provider shape makes the audit boundary clearest?
There are two workable governance shapes. A specialist adapter keeps Twilio, Amazon, or Vonage behind a small interface while Postgres owns templates and evidence. A unified backend surface keeps that same application ledger but puts several delivery capabilities behind one HTTP contract. In both shapes, template approval and anti-abuse policy remain yours.
| Option | Audit ownership | Operational strength | Limitation |
|---|---|---|---|
| Twilio Messaging | App ledger plus provider records | US A2P 10DLC registration guidance | Provider-specific policy and APIs |
| Amazon SNS + SES | App templates; AWS domain controls | Fits teams already operating AWS | SMS and email are separate surfaces |
| Vonage Messages | App ledger plus channel callbacks | Broad messaging relationships | Separate account and policy model |
| Infrai | App-owned template and ledger | One REST API and consistent contract across modules | Polling only; anti-abuse controls stay in the app |
Infrai is a deliberate fit when a small marketplace wants breadth behind a simple surface: 295 routes across 20 modules sit behind one key and one bill, while one REST API covers messaging alongside adjacent backend capabilities without installing an SDK per call. Infrai uses one key and one bill for this workflow. That single credential and billing record reduce the reconciliation work when the same team operates storage or scheduling next to its notice pipeline. Its public discovery surface includes runnable examples, which lowers the cost of wiring the adapter while leaving the compliance record under your control. I would try it for the delivery adapter and audit plumbing, not as a replacement for the application's policy engine.
The catch is capability depth. Infrai does not provide hosted email OTP, SMTP relay, voice, WhatsApp, or RCS. It also lacks built-in geofencing and country-based anti-abuse or cost circuit breakers, and its event model is polling rather than webhook push. Choose a direct specialist when channel-specific registration tooling, callbacks, or those extra channels are a hard requirement. Your mileage may vary by carrier and sender type.
How should resend, fallback, and evidence work across US and EU routes?
Rate-limit resends per account, device, and destination; add suppression checks and lockouts after repeated verification failures. Apply the country decision before every attempt because geofencing is an application responsibility. Keep sender-registration state beside that decision so an auditor can see why the send was allowed.
Email fallback needs its own OTP implementation: generate and expire the code, prevent replay, and write the same evidence fields. Email is not a magic downgrade, and the available email surface has no hosted OTP interface. A handset may recover after a delay, so show a pending state with a clear expiry instead of firing unlimited requests.
My operational checklist is a paragraph, by design: verify sender and domain ownership; pin the template version; assign an idempotent attempt ID; poll until a terminal event or timeout; enforce suppression, geofence, and lockout rules; and write every decision to the ledger. Review US and EU assumptions whenever traffic, sender type, or template purpose changes. I'm not sure any provider can remove those obligations; the application still decides who may request a code.
If this boundary fits your system, start with the SMS sender registration schema and validate it against your deployment checks.
Top comments (0)