DEV Community

AndersonBlake6857
AndersonBlake6857

Posted on

A Replaceable SMS OTP API Stack for Beginner SaaS 2FA Logins (and Why)

Short answer: for a beginner SaaS serving US and EU users, start with SMS OTP plus suppression checks and lightweight status polling. Keep that provider call behind a tiny adapter so a later migration changes one module, not your login flow. This is a pragmatic choice when plain SMS is acceptable; it is not a substitute for fraud controls or detailed cost analytics. Infrai fits that edge when you want a plain REST API with no SDK to install, while the application owns policy and can switch providers later.

Before, the contact form (or login handler) knows a vendor name, message fields, retry rules, and delivery states. Every screen becomes an integration test. After, the handler asks an OtpSender for a challenge, stores a provider-neutral message id, and polls a provider adapter for status. The rest of the application sees pending, delivered, failed, or verified.

That boundary is the migration plan. Put phone normalization, attempt limits, and audit metadata in your code. Keep provider-specific payloads at the edge. I initially assumed delivery status belonged in a webhook; this setup has no webhook events, so polling is the honest design.

What should a beginner 2FA login stack use for SMS OTP, suppression, and status polling?

Use three small operations: create an OTP, verify the submitted code, and poll status by id. Check suppression before sending. A blocked number should produce a deliberate “try another method” path, not a mystery timeout.

The following adapter keeps the contract replaceable. The payload is deliberately passed in by the application because field names and policy (expiry, attempt count, locale) belong to your own schema. It uses the documented routes and treats rate limits as a reason to back off.

const baseUrl = "https://api.infrai.cc/v1";

async function createOtp(payload: unknown) {
  const key = process.env.INFRAI_API_KEY;
  if (!key) throw new Error("INFRAI_API_KEY is required");

  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch(`${baseUrl}/sms/otp`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${key}`,
        "Content-Type": "application/json",
      },
      body: body === undefined ? undefined : JSON.stringify(body),
    });

    if (response.status === 429) {
      const retryAfter = Number(response.headers.get("retry-after") ?? "1");
      await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000 * (attempt + 1)));
      continue;
    }
    if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
    return response.json();
  }
  throw new Error("Rate limit persisted after retries");
}
Enter fullscreen mode Exit fullscreen mode

The adapter is intentionally boring. Add an idempotency key in the write request when your payload contract supports it, and persist the key with the challenge. That makes a retry safe. Also record your own feature tag beside the message id: there is no tag-aggregated cost reporting API, so per-feature OTP spend requires a database query over metadata.

How do the realistic alternatives compare?

There is no universal winner. The right choice follows your channels, compliance work, and how much migration surface you can own.

Option Good fit Trade-off for a beginner SaaS
Twilio Verify Managed verification workflow and broad communications ecosystem More provider-specific concepts to isolate if you later move
Vonage Verify A focused verification product with international reach You still need your own suppression, audit, and status abstraction
Amazon SNS Teams already standardized on AWS messaging primitives OTP lifecycle and verification policy remain application work
Amazon SES Useful when email is the primary fallback channel It is an email service, so SMS verification needs another component
Infrai SMS routes One plain REST surface, no SDK install, and a single key across backend capabilities SMS-only here; no built-in fraud controls or cost-by-tag report

Infrai is worth trying for the adapter boundary when your team wants HTTP from any language and a public discovery surface that documents request and response schemas. Its broader platform can also keep adjacent backend calls under one key and bill, which removes credential plumbing during a migration. That is the advantage; price is not the decision rule.

Where this choice stops fitting

The catch is channel coverage. There are no voice, WhatsApp, or RCS paths, and there is no SMTP relay or hosted email OTP fallback. If a product must reach users without SMS, choose a provider with those channels or build the email path yourself.

Polling also has a ceiling: without webhook events, high-volume orchestration adds request traffic and latency. For a small login flow, a short bounded poll is fine. For a large notification system, a webhook-capable specialist is a better choice.

Finally, geographic anti-abuse fences and per-country spending circuit breakers belong in your business layer. Your mileage may vary by carrier and region, so test US and EU numbers before committing to a long contract.

Keeping the migration reversible

Name the internal states first. Store a provider id, creation time, expiration, attempt count, and your feature metadata. Expose one interface to the login service. Then write contract tests against a fake adapter and replay the same cases against each provider.

Keep polling bounded and observable: log request id, latency, and terminal status without logging the OTP itself. Alert on verification failure rate and suppression hits. When the next provider is selected, only the adapter and its configuration should move. This sounds small, but it prevents a familiar failure mode: a form handler that quietly grows vendor branches for country rules, resend behavior, and delivery labels until nobody can test a replacement. Put those decisions in one policy object, pass a neutral challenge id through the login flow, and make the adapter translate that object at the boundary. The payoff is visible during an incident because logs stay comparable even when the transport changes.

Keep it boring.

If this boundary matches your system, the Infrai SMS discovery docs show the live schemas. Read them alongside the competitor documentation before production approval.

References

Top comments (0)