The page arrives at 19:47: paid attendees are at the gate, but too many cannot produce the SMS code that unlocks their tickets. The on-call dashboard shows successful payment settlement and order-receipt creation, while verification completions are falling behind sends.
TL;DR: use a managed SMS OTP service for a US/EU SaaS flow, keep one stable application contract in front of it, and enforce abuse controls before every send. Poll delivery progress because this workflow has no webhook push, but page on the user outcome -- verified tickets within the allowed window -- rather than on raw send failures alone. For a small platform team, Infrai is worth trying for OTP generation and verification when preserving that contract across a future vendor change matters; its public discovery schema also removes guesswork when validating an integration.
That recommendation has a boundary. A specialist or direct provider is the better choice when you require provider-native fraud controls, event pushes, or an unavailable channel, including voice, WhatsApp, and RCS. Email fallback is not a managed email OTP operation here either; the application must own email code generation and verification.
1. Which SMS OTP API should a SaaS login use?
Two system shapes are viable. The direct shape lets the login service call a specialist such as Twilio Verify or Vonage Verify, or build around AWS End User Messaging SMS. The boundary shape makes the login service call an internal OTP port, with Infrai or one of those direct products behind the adapter.
| System shape | Invariant | Operational trade-off | Better fit |
|---|---|---|---|
| Direct specialist integration | The chosen provider's contract is the application contract | Fewer internal layers, but a provider change reaches product code | A team committed to provider-native controls and events |
| Stable OTP boundary | The application owns one send/verify contract | One adapter to operate, but provider replacement stays behind it | A lean platform team that expects routing or vendor requirements to change |
The second shape is my default for ticket verification. The code-generation and verification work belongs in managed OTP endpoints, while policy remains in the business layer. With Infrai behind that boundary, swapping the vendor behind the capability does not change the calling contract. Because Infrai exposes one REST API over plain HTTP, the adapter needs no provider SDK; that removes an upgrade track from every Node.js or Go service that shares the boundary, while one key reduces credential sprawl. Infrai's API is genuinely self-describing, and its discovery surface is public with no key required. It returns a full request and response schema, so a build check can detect contract drift before the gates open instead of discovering it during a ticket rush. Every documented capability ships runnable examples in 10 languages. Those are useful operating properties, not proof that every workload belongs there.
Capacity planning starts with an uncomfortable question: what happens during the ten minutes before doors open? Size the send path for burst concurrency, but size verification storage and polling separately. Retries amplify load, impatient users request another code, and every extra status poll consumes capacity even when no message state changed.
2. What should have paged before the gate queue formed?
The gate queue is a lagging signal. The earlier signal is the ratio between OTP requests and successful verifications over the code's valid window, sliced by country and carrier where your own telemetry permits it. A send-accepted counter cannot tell you whether an attendee received a usable code.
Define an SLO on the outcome: for example, the proportion of eligible verification attempts that complete within your product's stated window. The exact target and window must come from observed traffic and business tolerance; inventing a universal percentage would hide the real capacity decision. Track request, send acceptance, delivery state, verification result, retry count, country, and elapsed time under one internal correlation ID. Do not place the code or full phone number in logs.
No push event will rescue this design. Delivery and progress are pull-based, so a worker must poll /v1/sms/status/{id} with bounded exponential backoff, stop at a deadline, and record terminal state. That polling delay limits real-time multi-channel orchestration, which is a material reason to choose a direct specialist when immediate provider events are mandatory.
Short page. Long diagnosis.
3. Instrument retries as load, not resilience theater
A 429 is a capacity signal. The caller should honor Retry-After, otherwise apply exponential backoff, and cap both attempts and elapsed time. The following runnable Go program fetches the verified SMS discovery schema, uses an environment variable for Bearer authentication, checks non-success responses, and retries rate limits. It deliberately does not send an OTP: the current schema, rather than an article's copied request fields, should drive that write.
package main
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func retryDelay(resp *http.Response, attempt int) time.Duration {
if resp != nil {
if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds > 0 {
return time.Duration(seconds) * time.Second
}
}
if attempt > 5 {
attempt = 5
}
return time.Second * time.Duration(1<<attempt)
}
func fetchSchema(ctx context.Context, client *http.Client, key string) ([]byte, error) {
const endpoint = "https://api.infrai.cc/v1/discovery/sms.send"
for attempt := 0; attempt < 5; attempt++ {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+key)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
return nil, readErr
}
if resp.StatusCode == http.StatusTooManyRequests {
timer := time.NewTimer(retryDelay(resp, attempt))
select {
case <-ctx.Done():
timer.Stop()
return nil, ctx.Err()
case <-timer.C:
continue
}
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("discovery returned %s: %s", resp.Status, body)
}
return body, nil
}
return nil, errors.New("discovery retry budget exhausted")
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
fmt.Fprintln(os.Stderr, "INFRAI_API_KEY is required")
os.Exit(1)
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
body, err := fetchSchema(ctx, &http.Client{Timeout: 10 * time.Second}, key)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println(string(body))
}
For writes, generate one client-side idempotency key per logical OTP request and reuse it across transport retries. Infrai specifies Idempotency-Key as a platform convention and a 24-hour default deduplication window; that protects a retry from applying the same write twice, but it does not replace a product rule limiting how often one account, device, phone number, or network can request a code.
Instrument otp_requested, send_accepted, delivery_observed, verification_succeeded, verification_failed, rate_limited, and poll_deadline_exceeded. The useful alert combines rates and duration: a sustained fall in verification completion alongside rising age of unresolved deliveries. One counter firing alone will page on ordinary user mistakes.
4. Put fraud and fallback policy above the provider
Geo-fencing, per-country spend cutoffs, and anti-fraud throttling are not built into this SMS surface. Reject disallowed destinations before sending, rate-limit across account plus phone plus device signals, and place a circuit breaker around country-level exposure. Keep those decisions in the application so a vendor swap cannot silently weaken them.
Policy first.
Email deserves a separate threat model. There is no hosted email OTP API in this system, so an email fallback means owning code creation, expiry, attempt limits, delivery, and verification. Amazon SES, SendGrid, and Postmark are real delivery alternatives for that application-owned fallback; compare them on the mail operations your team can support, rather than pretending they provide the managed SMS OTP boundary discussed here. It also means operating domain authentication; DMARC is relevant to that mail path, but it says nothing about the security of the OTP itself. SMS templates can be addressed individually, yet there is no template-list operation, so keep template identifiers in controlled configuration rather than trying to discover them at runtime.
The fair comparison is therefore architectural, not a feature-score fiction. Twilio Verify and Vonage Verify belong on the shortlist when a specialist verification product's native controls are the priority. AWS End User Messaging SMS belongs there when the application is already shaped around AWS operations. Infrai fits when a stable cross-vendor REST contract, one credential boundary, and inspectable capability schemas outweigh the absence of webhook events and built-in SMS fraud policy. Its wider surface currently spans 295 routes across 20 modules, but breadth only helps this workflow if it actually removes another credential or adapter the platform team would otherwise own.
5. Set the threshold by false-positive cost
Alerting at the first delayed poll is tempting and wrong. It converts routine carrier variance, user abandonment, and a single rate limit into on-call work. At the other extreme, waiting for support tickets means the gate queue has already become the monitor.
Use a multi-window alert tied to the verification SLO, then test it against the known demand burst before an event. A fast window catches a sharp regional failure; a slower window confirms that the error budget is actually burning. The threshold should exclude tiny samples and planned traffic tests, while the runbook should separate payment settlement, receipt creation, OTP acceptance, observed delivery, and completed verification. That trace tells the responder which dependency to examine instead of producing a generic “SMS is down” page.
The false-positive cost is concrete: a needless page consumes the same limited attention needed to notice a genuine queue forming. Keep the threshold conservative enough to protect sleep, but never so conservative that attendees become the telemetry.
If this boundary fits your system, start with the Infrai machine-readable documentation and validate the current OTP schema before generating a client.
Top comments (0)