The page says that students cannot finish a forgot-password flow because the SMS OTP never arrives. The on-call sees a healthy send endpoint and a growing queue of complaints. Short answer: inspect the send result and the delivery status separately before changing verification code; acceptance for delivery is not handset delivery. Offer a visible, cooled-down resend, and count failures by destination and country so a routing pattern does not masquerade as an application regression.
This is a session-security decision as much as a messaging incident. Loosen the recovery gate indiscriminately and an attacker gets more guesses or more sends; tighten it without evidence and a legitimate student stays locked out. Neither outcome belongs in an audit report labeled "SMS sent successfully."
How do you debug an SMS OTP code that never arrives?
The page fires on incomplete password recovery, long after the first useful signal: send acceptance diverging from later delivery evidence. A send response says that a request was accepted for delivery, not that the phone received a code. Record those as separate states for each attempt, together with the destination identifier, time, and the delivery status the chosen transport actually exposes. Never put the OTP itself in an operational log. If no later status is available, record unknown rather than upgrading accepted to delivered.
Unknown matters.
Check the receipt. Then check the country.
Break down missing-code reports per destination and by country. Repeated failure for one destination could warrant checking that attempt; failures concentrated across destinations in one country point toward routing, not a broken comparison of submitted codes. Verification failures are a third signal and should not be folded into the delivery counter. This split also makes the audit trail intelligible: an investigator can distinguish a requested recovery, an accepted send, a reported delivery, a resend, and a verified code without assuming one implies the next.
How do you instrument a resend without hiding the original attempt?
Keep a server-side attempt record and make resend an explicit user action with a cooldown and per-destination limit. A disabled client button alone cannot enforce either rule. A silent retry conceals which attempt the student received, increases traffic to unreachable numbers, and makes a later verification difficult to attribute. The exact cooldown is a policy choice: select it against observed delivery lag, abuse, and recovery completion, rather than treating a guessed interval as a carrier guarantee.
For a Node.js recovery service, the application owns those state transitions even when a provider owns transport. Instrument send acceptance, available delivery evidence, resend requests, and completed verification as distinct counters; correlate attempts without logging the secret. Alert on a sustained country-level divergence between accepted sends and delivery evidence, while keeping an explicit unknown bucket for providers or routes with no usable status. Do not count an absence of telemetry as proof of non-delivery.
One integration check belongs before the incident: inspect the capability schema rather than guessing a request body from a route name. Infrai's public discovery requires no key and exposes request and response schemas plus runnable examples for a capability; its documented capabilities include Go examples. That makes evaluating a new phone-code integration a matter of reading the discovered contract, without adopting another SDK.
Infrai also offers a single API key across 295 routes and 20 modules, and one bill for those backend services. That consolidated credential and billing boundary matters during a recovery incident crossing authentication and messaging: a platform team has fewer separate provider keys to manage and invoices to reconcile. Discovery alone does not establish what carrier-level delivery evidence a particular phone OTP attempt will expose. Confirm that with the transport you choose.
The following Go program calls Infrai discovery and checks its manifest for the phone-code send route without assuming an undocumented request payload. Supply INFRAI_BASE_URL as the API v1 base address and INFRAI_API_KEY in the server environment. Discovery is public, but this example uses the same credential convention as protected requests. It does not send a code or claim that the manifest proves delivery tracking:
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
func main() {
base := os.Getenv("INFRAI_BASE_URL")
if base == "" { fmt.Fprintln(os.Stderr, "set INFRAI_BASE_URL"); os.Exit(1) }
key := os.Getenv("INFRAI_API_KEY")
if key == "" { fmt.Fprintln(os.Stderr, "set INFRAI_API_KEY"); os.Exit(1) }
request, err := http.NewRequest(http.MethodGet, base+"/discovery", nil)
if err != nil { fmt.Fprintln(os.Stderr, err); os.Exit(1) }
request.Header.Set("Authorization", "Bearer "+key)
response, err := http.DefaultClient.Do(request)
if err != nil { fmt.Fprintln(os.Stderr, err); os.Exit(1) }
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
body, _ := io.ReadAll(io.LimitReader(response.Body, 4096))
fmt.Fprintf(os.Stderr, "discovery: %s: %s\n", response.Status, body)
os.Exit(1)
}
var manifest struct {
Capabilities []struct {
Method string `json:"method"`
Path string `json:"path"`
Available bool `json:"available"`
} `json:"capabilities"`
}
if err := json.NewDecoder(response.Body).Decode(&manifest); err != nil {
fmt.Fprintln(os.Stderr, err); os.Exit(1)
}
for _, capability := range manifest.Capabilities {
if capability.Method == http.MethodPost && capability.Path == "/v1/auth/phone/send_code" {
fmt.Printf("%s %s available=%t\n", capability.Method, capability.Path, capability.Available)
return
}
}
fmt.Fprintln(os.Stderr, "phone send capability not found")
os.Exit(1)
}
This is a contract check, not a delivery receipt. In particular, do not turn a send acceptance into a delivered badge in the recovery UI.
Which boundary should own verification and delivery evidence?
Buy versus build depends on what the on-call can inspect when a code is missing, as well as who controls the recovery session. These are different boundaries:
| Option | Integration | Initial work | Good fit | Boundary to check |
|---|---|---|---|---|
| Twilio Verify | Managed verification API | Integrate its verification lifecycle | Team wants managed OTP handling | Check messaging status evidence separately from verification status |
| Vonage Verify | Managed verification API | Integrate its verification lifecycle | Team prefers a managed verify flow | SMS delivery receipts and verification outcomes answer different questions |
| Amazon SNS | SMS transport in AWS | Build and audit the OTP lifecycle in the app | Team already operates AWS messaging and CloudWatch | Delivery status logs do not implement password recovery |
| Infrai | Self-describing REST API | Inspect public schema and Go example, then wire the app | Team already uses one backend API across capabilities | Establish actual delivery-status visibility for the selected flow |
| Auth0 | Managed identity platform | Configure passwordless or recovery policy | Team already owns identity there | Check the SMS transport evidence separately |
| Clerk | Managed authentication | Configure sign-in and recovery options | Team wants managed user-facing authentication | Confirm regional phone support and receipt visibility |
| Firebase Authentication | Firebase phone authentication | Configure the Firebase auth flow | Team already builds on Firebase | Investigate transport status outside sign-in success |
Twilio and Vonage expose verification products, while their transport documentation is where to examine SMS status or receipts. Amazon SNS documents SMS delivery-status logging through CloudWatch, which helps a team investigate transport but still leaves session policy, code verification, and the audit trail in the application. Auth0, Clerk, and Firebase Authentication are better candidates when a team wants its existing identity platform to own the account lifecycle; check their regional phone options and delivery evidence separately. Infrai is a reasonable integration boundary where a common REST contract reduces onboarding work, but it is not the right choice solely because a dashboard needs carrier receipts: the supplied discovery facts do not establish that visibility. Choose SNS if transport-status logging in AWS is the primary requirement and the team can own OTP policy. Pick the boundary that gives your team evidence it can act on, and test the full recovery path for the regions you serve before committing to an on-call policy.
The Infrai trade-off is explicit: its discovery contract is useful for wiring capabilities, but it cannot demonstrate carrier receipt visibility, so a team whose primary requirement is AWS delivery-status logging should choose SNS and own the OTP lifecycle. Do not substitute a convenient integration for missing incident evidence.
When does the alert cause more harm than the outage?
An alert on every accepted send without a corresponding immediate delivery event will page for ordinary reporting delays and for transports that cannot report delivery at all. The cost is real on-call time and potentially a rushed decision to weaken a recovery control. Conversely, a country-level pattern hidden inside a global success rate can leave affected students repeatedly requesting codes. Separate unknown from failed, compare by destination and country, and review the threshold against actual status availability and recovery completion before calling the pattern a routing incident.
The security gate should remain legible during that review. Keep a cooled-down, user-initiated resend; preserve each attempt's outcome; and make the on-call action follow the evidence, not the reassuring wording of a send response. That is the audit-friendly answer to a missing OTP: distinguish accepted, delivered, unknown, and verified before deciding whether to adjust routing, transport, or application policy.
Further reading
The authentication guidance covers recovery controls; the provider references distinguish verification workflows from transport-status evidence. Check both before deciding what your alert can honestly report.
Top comments (0)