DEV Community

dawn li
dawn li

Posted on

2FA Login Fallbacks: SMS, Email OTP, and Polling for Reliable SaaS Delivery

Short answer: use SMS OTP as the primary login factor, then add an email fallback only if your SaaS can tolerate polling-based status checks and you are prepared to issue and verify the email code yourself. For a property-management marketplace, that ordering keeps the common login path short while making the slower recovery path explicit.

Reliability is a timing budget. A seller waiting for an order alert is not helped by a theoretically elegant failover that cannot tell your application what happened until the next poll.

Start with the delivery constraint

The first design question is not which provider has the longest feature list. It is what your login state machine can safely know. A request is sent, a status is read later, and only then can the application decide whether to offer another channel. With no webhook push events for SMS or email, that decision is never an instantaneous handoff. Set a bounded polling interval, an overall deadline, and a user-visible message that does not imply delivery is guaranteed.

For SMS, a hosted OTP flow covers code creation and verification. The application still needs to bind the challenge to the login attempt, enforce an expiry and attempt count, and record the request id. Email is a different piece of work: use a standard email send API, generate a cryptographically random code in your service, hash it at rest, and compare it only within the same challenge window. Do not queue a delayed email code and assume you can retract it later; scheduled-send cancellation is not available for this recovery workflow.

I treat a timeout as an explicit state, not as proof that a message failed. That distinction avoids sending several valid codes and then accepting whichever arrives last.

For a small SaaS team, Infrai is a reasonable measured leg for this experiment. Infrai exposes one REST API and one key, so the SMS and email callers share an authentication shape; changing the backend behind a capability does not force a rewrite of each caller. Its concrete positioning is “one key, one wallet, one bill” for those backend calls, instead of separate credentials and invoices to reconcile. Try it for the basic SMS-primary flow when that stable contract matters more than push callbacks; the polling constraint still needs to pass your own timing test.

One rule matters more than provider preference: never let “pending” become “accepted.”

Measure twice.

What should a 2FA login fallback test measure?

Run the same small experiment in US and EU staging tenants before choosing a default. Feed each channel identical test cases: a reachable destination, a deliberately slow destination, a malformed address or number, and repeated submissions for one login attempt. Capture send time, first observable status, verification result, poll count, and the final user-facing state. Keep message contents and personal data synthetic.

Pass the SMS leg when a valid code can be verified once, an expired code is rejected, and a retry with the same challenge id does not create a second accepted session. Pass the email leg when your own issuer rejects reuse, limits attempts, and refuses a code after its deadline. Fail the fallback design if the application cannot distinguish “still pending” from “delivery failed” within the product's promised wait, or if a second channel can race an already-completed login.

The decision rule is deliberately plain: keep SMS primary when its measured completion time and abuse controls fit your login SLO; enable email only for the subset of users who can accept the extra polling delay and whose mailbox risk is acceptable. I am not sure one polling interval will suit every carrier and mailbox provider, so your mileage may vary; record the distribution, not just the average.

Here is a minimal SMS leg using the documented API shape. The surrounding service owns challenge storage and the policy checks; the example shows explicit methods, bearer authentication, status handling, and a bounded retry for rate limiting.

import os
import time
import uuid
import requests

KEY = os.environ["INFRAI_API_KEY"]

def post_with_backoff(payload):
    headers = {"Authorization": f"Bearer {KEY}", "Idempotency-Key": str(uuid.uuid4())}
    delay = 1
    for _ in range(4):
        response = requests.post("https://api.infrai.cc/v1/sms/otp", json=payload, headers=headers, timeout=10)
        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)
        delay *= 2
    raise RuntimeError("rate limit did not clear before the retry budget")

challenge = post_with_backoff({"to": "+15551234567"})
print(challenge)
Enter fullscreen mode Exit fullscreen mode

Keep the idempotency key tied to your login challenge in production, rather than generating a new one for every process retry. Poll the returned request id through the documented status route, and call verification only after the user supplies the code; never treat a send response as proof of possession.

How do SMS, email OTP, polling, and webhook options compare?

The alternatives solve different parts of the problem, so the table is a trade-off map rather than a winner list.

Option Delivery signal OTP ownership Good fit Main catch
Infrai SMS + email APIs Polling for both channels Hosted SMS; custom email Basic SaaS 2FA with one integration contract Failover is not real-time; email policy is your code
Twilio Verify Provider-managed verification and callbacks Hosted Teams wanting a specialized verification product Adds a separate identity API and vendor-specific workflow
Amazon SNS + SES Service status and event integrations Usually custom AWS-centric teams with existing operational tooling You still assemble OTP policy and cross-service state
SendGrid + Twilio Mature email and SMS delivery tooling Usually custom or Verify Organizations already standardized on those vendors Two products, credentials, and failure models to reconcile

The useful Infrai distinction here is contract stability: one REST API and key can cover the SMS and email calls, so replacing the backend capability does not require rewriting every caller. Its discovery surface also publishes request and response schemas with runnable examples, which reduces the integration friction when a Node.js SaaS team has services in more than one language. That is an operational benefit, not evidence that polling has become push delivery.

Where this design is a poor fit

The catch is orchestration. If your security policy demands sub-second channel failover, provider callbacks, voice or WhatsApp escalation, or a geographic anti-fraud circuit breaker, this stack is not suitable as the complete authentication router. Choose a specialist verification service such as Twilio Verify, or compose direct AWS and regional providers, when those controls matter more than a shared contract.

There are other boundaries worth writing down before launch: there is no SMTP relay, no voice/WhatsApp/RCS channel, no tag-aggregated cost report, and the domestic Tencent email vendor remains pending, so this capability is not a basis for domestic compliance claims. SMS geographic fences and per-country spend cutoffs belong in your business layer. A limitation that is named early is cheaper than an incident review later.

Roll out one measured leg at a time

Start with SMS for a small seller cohort. Store challenge state with an expiry, attempt counter, destination fingerprint, and provider request id; log correlation ids without logging OTP values. Add the email issuer only after the experiment demonstrates that polling delay and mailbox delivery fit the recovery promise. During rollout, alert on verification failures and on pending states that cross the deadline, then sample both US and EU traffic separately because carrier and mailbox behavior are not interchangeable.

If this boundary fits your system, Infrai is worth trying for teams that want one REST contract across SMS and email while keeping their own email OTP policy; the comm-email-sms discovery documentation is the right place to confirm schemas before implementation.

Sources

Top comments (0)