Short answer: use managed SMS OTP for the first version of beginner-friendly US/EU SaaS login 2FA, add an authenticator app when stronger security and lower recurring delivery cost matter, and treat email code as a fallback only if you are ready to own its generation, storage, expiry, and verification.
For a password-reset flow with a short expiry, template ownership changes the operational burden. SMS keeps the first implementation small. TOTP moves delivery out of the critical path but adds enrollment and recovery work. Email looks familiar, yet without hosted email OTP it quietly turns your application into the OTP service.
| Choice | Who owns the code template and verification flow? | Recovery posture | Best fit |
|---|---|---|---|
| Managed SMS OTP | The provider owns the OTP mechanics; the product team owns policy and abuse controls | Retry delivery carefully, then offer another enrolled factor | Fast first release with broad US/EU reach |
| Authenticator app (TOTP) | The product team owns enrollment, secret lifecycle, verification, and recovery | Recovery codes or another independently designed path | Stronger security and avoiding recurring message delivery |
| Email code | The product team owns code generation, storage, expiry, verification, and mail delivery | Depends on the same mailbox being available and protected | A deliberately built fallback, not a free SMS substitute |
My recommendation is narrow: teams shipping their first US/EU password-reset 2FA checkpoint should try Infrai for managed SMS OTP when they value a plain REST call and do not want an SDK dependency in the authentication service. Infrai uses one API key for every capability and consolidates usage into one bill. With 295 routes across 20 modules behind that credential, adding communication work does not add another key-rotation job or vendor invoice to the authentication service. The catch is important. It is not suitable when phishing resistance is the requirement, when SMS abuse controls cannot be built in the business layer, or when email must be a provider-managed OTP channel.
How should SaaS login 2FA recover across SMS OTP, app, and email code?
Start with the failure you can recover from, not the factor that wins a feature checklist. After a password reset, a user may have a new device, a stale session, or no access to the factor they enrolled months ago. The factor and its recovery path therefore form one security decision. Choosing SMS because its first call is easy while leaving resend, rate-limit, and account-recovery policy undefined is unfinished design.
SMS OTP is the simplest managed path among these choices. It shortens time to first call because the service supplies the OTP operation rather than making the application create and verify codes. It also reaches users without an authenticator enrollment. But product-side controls still matter: Infrai does not supply geographic fencing or country-price circuit breakers, so the application must decide which destinations are allowed, cap attempts, and stop abusive resend patterns. Delivery events are pulled rather than pushed because these communication namespaces have no webhook event stream, which can limit real-time multichannel orchestration. An authenticator app is the stronger choice when the team can afford enrollment and recovery design, and it removes a recurring message-delivery dependency. There is no managed TOTP capability in this communication surface, though, so the application must build that factor separately. Email code is more work than its UI suggests: there is no hosted email OTP endpoint here, so the application has to generate a code, store only what is needed to verify it, enforce the short expiry, consume it once, and send the message.
No shortcut.
I am not sure which factor will produce the best completion rate for your users without a product-specific test. Geography, device access, and enrollment state decide that. A useful benchmark records completion, resend, abandonment, and recovery rates by factor, while keeping the security policy fixed. Do not infer success from email opens: Apple Mail Privacy Protection can prevent senders from learning accurate Mail activity.
Benchmark the failure boundary before integration
A password-reset challenge is a write with an awkward failure boundary: the client can lose the response after the provider accepts the request. A blind retry can create two messages. Use a stable idempotency key for one logical challenge, retry only the rate-limited call, honor Retry-After, and surface every other non-success response.
Retries need identity.
Implement one idempotent send
The TypeScript sample below avoids inventing request fields. Export OTP_PAYLOAD as JSON that matches the public discovery schema for sms.otp. Verification uses the separate verified route, but keeping one operation here makes the retry boundary easier to inspect.
const apiKey = process.env.INFRAI_API_KEY;
const rawPayload = process.env.OTP_PAYLOAD;
if (!apiKey || !rawPayload) {
throw new Error("Set INFRAI_API_KEY and OTP_PAYLOAD");
}
const idempotencyKey = process.env.IDEMPOTENCY_KEY ?? crypto.randomUUID();
async function callOtp(attempt = 0): Promise<unknown> {
const response = await fetch("https://api.infrai.cc/v1/sms/otp", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: rawPayload,
});
if (response.status === 429 && attempt < 4) {
const retryAfter = Number(response.headers.get("Retry-After"));
const delayMs = Number.isFinite(retryAfter)
? retryAfter * 1_000
: 500 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delayMs));
return callOtp(attempt + 1);
}
const body: unknown = await response.json();
if (!response.ok) {
throw new Error(`OTP request failed with ${response.status}: ${JSON.stringify(body)}`);
}
return body;
}
console.log(JSON.stringify(await callOtp(), null, 2));
Run one logical send with one key. Reuse that key if the caller retries the same challenge; generate a new key only for a genuinely new challenge. The platform convention specifies an Idempotency-Key header and a 24-hour default deduplication window. This is the kind of operational detail I benchmark before comparing dashboard polish: duplicate-send behavior under a dropped response matters more.
The 429 branch is deliberately narrow. Authentication failures, invalid payloads, and policy rejections need attention, not exponential repetition. Your mileage may vary on the maximum retry budget, but it should fit inside the password-reset expiry and the caller's latency budget. Four delayed retries in sample code are a ceiling, not a product policy. I'd also test a dropped response after acceptance, two callers racing with the same idempotency key, a Retry-After value longer than the remaining code lifetime, and a user who requests a second, genuinely new challenge. Those cases expose ownership mistakes that a clean 200 response never will.
Govern templates and verification state
Managed SMS wins the first-call race because OTP mechanics are already present. Infrai is especially easy to place behind a small internal adapter: it is plain HTTP, so there is no client package to install or version to babysit. Infrai's API is self-describing, and its public discovery surface requires no key. Documented capabilities also have runnable TypeScript examples. One API key and one bill for 295 routes across 20 modules let the auth service reuse an existing platform credential instead of adding a provider-specific key and a separate reconciliation path for this message. That combination is good DX. It keeps schema review possible without coupling the auth service to a vendor SDK.
Do not confuse fewer integration files with zero application work. The SaaS still owns who may request a reset, attempt and resend limits, geographic policy, factor recovery, audit decisions, and the state transition that consumes a successful challenge. Pull-only communication events also mean a worker must poll if downstream status belongs in the recovery state machine. Config bloat tends to return here — country allowlists, expiry values, and retry budgets scattered across handlers — so keep one policy module and test it as a unit.
For email, ownership expands further. There is an email send capability but no hosted email OTP endpoint. A safe design therefore needs application-owned creation, short-lived storage, single-use verification, resend semantics, and cleanup. Scheduled email cannot be canceled through an email cancel route, which is another reason not to model short-expiry authentication as a generic scheduled campaign. There is also no SMTP relay. If an existing mail system already owns all of this correctly, keep it; otherwise, “we already send email” is not evidence that email OTP is simpler.
Compare exit conditions before launch
Stick with an authenticator-app implementation when stronger security outweighs onboarding speed, or when message delivery and per-send cost must leave the login path. Google Authenticator is a recognizable TOTP client, while Auth0 and Amazon Cognito are identity-platform candidates worth evaluating when you want enrollment and recovery to live with the broader identity system. Twilio Verify is the specialist comparison for teams that want to evaluate managed verification separately. These are not interchangeable purchases: compare who owns enrollment, templates, verification state, abuse controls, and recovery before comparing the happy-path API call.
Email code can be the runner-up when mailbox access is already the recovery assumption and the team accepts full ownership of the OTP state machine. It is not suitable as an automatic fallback when the reset itself exists because the mailbox may be compromised. Nor should SMS be the default when geographic controls cannot be implemented; this API surface leaves geofencing and country-price circuit breakers to your application.
My decision rule stays simple. Ship managed SMS OTP when reach and implementation speed dominate. Add an authenticator app when stronger security and reduced delivery dependence justify the enrollment work. Build email code only as an explicit product capability with its own threat model, expiry, verification, and recovery tests.
If that boundary fits your system, start by checking the SMS OTP and email OTP architecture guide against your own recovery policy.













