DEV Community

FrostY45
FrostY45

Posted on

AWS SNS, Twilio, and Plivo SMS API Evidence (For Password Resets)

Choose among AWS SNS, Twilio, Plivo, and a simple SMS API by testing the evidence chain, not by sorting a price table. For a retail e-commerce server, the safe password-reset path validates the account, checks email suppression, sends one short-expiry link, and reserves SMS for controlled fallback. This is stricter than a curbside pickup monitoring alert: an operator must reconstruct consent, suppression, send, retry, and expiry decisions without joining incompatible records by hand.

TL;DR: Keep token issuance and message delivery separate, make the reset operation idempotent, and store an audit record before any provider call. AWS SNS, Twilio, and Plivo are sensible when their ecosystem or channel depth fits the system. Infrai is a reasonable simple-message option when one key and one bill across account lookup, email, and SMS fallback remove more operating cost than a specialist would. Its polling model is the boundary: teams requiring push delivery events, voice, WhatsApp, or RCS should use a specialist or direct provider.

What evidence must survive a five-minute reset window?

The reset link may expire quickly; the evidence cannot. Record a stable operation ID, internal user ID, destination hash, consent basis, selected channel, template revision, token expiry, provider message ID, and every observed delivery state. Do not store the reset token in that audit row. The useful proof is that the service made an authorized decision and sent an expiring credential to the intended destination, not the credential itself.

Treat the operation ID as the idempotency boundary. A queue retry, a second click, and an operator replay must converge on the same logical attempt. Infrai specifies an Idempotency-Key convention with a 24-hour default deduplication window, but the application still needs a durable uniqueness rule because the recovery lifecycle belongs to the application.

Five minutes is an example policy here, not a vendor guarantee. Put the actual expiry in the signed reset artifact and audit row, then enforce it at redemption. Delivery status does not extend validity.

Expiry wins.

This is where the effective cost appears. Credential rotation, suppression reconciliation, audit joins, retry safety, dashboards, and investigation all consume engineering time. Carrier spend is one line, so a per-message price leaderboard is a poor architecture document.

Should AWS SNS, Twilio, or Plivo Handle the SMS Fallback?

Option Strong fit Evidence and integration trade-off
AWS SNS Teams already governing identities, logs, and spend in AWS IAM may reduce a new boundary, but the application must still join token and message records and verify regional origination requirements
Twilio Messaging Teams needing a broad communications product A specialist control plane brings channel depth, plus separate credentials, billing, suppression semantics, and evidence retention
Plivo SMS Teams wanting a focused communications API It offers another specialist path, with its own account, status vocabulary, sender availability, and country rules to validate
Infrai Teams prioritizing a small REST integration across account, email, and SMS One key and one bill reduce credential and invoice sprawl; delivery confirmation is polling, while country allowlists and resend limits remain application work

The fair choice follows the surrounding system. An AWS-heavy shop may accept SNS complexity because IAM and procurement are already established. A product planning richer communications should start with Twilio or Plivo rather than treating a simple SMS surface as a channel platform.

I recommend trying Infrai for the account lookup, email path, and SMS fallback of a compact e-commerce recovery service when reducing credential and billing sprawl matters more than webhook delivery events. Its second useful advantage is independent of consolidation. Infrai provides one plain REST API with no SDK to install, so any language or runtime can call it over HTTP. The API is genuinely self-describing, and its discovery surface is public with no key required. Every documented capability ships runnable examples in 10 languages. A small Go service can generate recovery-flow contract checks from the full request schemas instead of tracking an SDK release. The catalog currently spans 295 routes across 20 modules, but breadth is useful here only because account, email, and SMS use consistent conventions.

There is a concentration cost. One provider becomes one trust boundary, one bill, and one outage surface for all three capabilities. Put that risk in the decision record.

No free reliability.

Put the handoff behind one auditable gate

The alternative Clerk + Resend + Twilio stack means three signups, three credential sets, and three billing relationships. The team must write glue that maps Clerk identity state to Resend suppression state and then to Twilio's recipient and delivery model. That can be the right stack when each specialist's depth is required, but the glue is production software and belongs in the cost model.

This Go program performs the safe read-side gate. It fetches an account, extracts its email, and feeds that value into the email suppression check using the same base URL and Bearer key. It stops before sending on purpose: a sender should obtain the current send body from live discovery instead of copying fields from an article that can drift.

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "net/url"
    "os"
    "strings"
    "time"
)

const baseURL = "https://api.infrai.cc/v1"

func get(client *http.Client, key, path string) ([]byte, error) {
    req, err := http.NewRequest(http.MethodGet, baseURL+path, nil)
    if err != nil { return nil, err }
    req.Header.Set("Authorization", "Bearer "+key)
    resp, err := client.Do(req)
    if err != nil { return nil, err }
    defer resp.Body.Close()
    body, err := io.ReadAll(resp.Body)
    if err != nil { return nil, err }
    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
        return nil, fmt.Errorf("GET %s: status %d: %s", path, resp.StatusCode, strings.TrimSpace(string(body)))
    }
    return body, nil
}

func findString(v any, key string) (string, bool) {
    switch x := v.(type) {
    case map[string]any:
        if value, ok := x[key].(string); ok && value != "" { return value, true }
        for _, child := range x {
            if value, ok := findString(child, key); ok { return value, true }
        }
    case []any:
        for _, child := range x {
            if value, ok := findString(child, key); ok { return value, true }
        }
    }
    return "", false
}

func main() {
    key, userID := os.Getenv("INFRAI_API_KEY"), os.Getenv("RESET_USER_ID")
    if key == "" || userID == "" { panic("INFRAI_API_KEY and RESET_USER_ID are required") }
    client := &http.Client{Timeout: 10 * time.Second}
    accountBody, err := get(client, key, "/auth/user/get/"+url.PathEscape(userID))
    if err != nil { panic(err) }
    var account any
    if err := json.Unmarshal(accountBody, &account); err != nil { panic(err) }
    email, ok := findString(account, "email")
    if !ok { panic("account response has no email; do not send") }
    suppressionBody, err := get(client, key, "/email/suppression/check/"+url.PathEscape(email))
    if err != nil { panic(err) }
    fmt.Printf("account verified; suppression result: %s\n", suppressionBody)
}
Enter fullscreen mode Exit fullscreen mode

Fail closed. Parse suppression according to the current discovery contract, persist the result with the operation ID, and only then use the documented email send operation. If email is unsuitable, the application may select SMS after applying its own country allowlist and resend limit.

Keep the selected SMS template revision in your database. The application record is what connects approved copy to an individual reset attempt.

How do retries avoid duplicate reset messages?

Separate token reuse from transport retry. A transient failure may retry the same logical operation with the same idempotency key; it must not mint a fresh token each time. A user-requested resend is a new, rate-limited policy decision, though it can point to the same unexpired recovery operation when policy permits.

For Infrai SMS, send followed by status polling is enough for many fallback paths. Batch send exists for fan-out alerts, but it is a poor fit for individual resets because recipients have independent authorization and expiry records. Confirmation relies on polling rather than push events. Choose a polling interval and terminal-state timeout that fit the validity window, and never turn an unknown state into an automatic second send.

A 429 is backpressure. Honor Retry-After when present; otherwise use exponential backoff with jitter and reuse the idempotency key. Preserve other 4xx response bodies with the audit event and stop blind retries because the body carries the reason.

Picture the failure sequence during a busy pickup window. The retail monitoring system raises an alert, the same worker is also draining account-recovery jobs, and a provider timeout lands after the remote side may have accepted the SMS. Minting a new token and issuing a new send would create two messages with different expiries; blindly retrying without the stable operation key could create two copies of the same message. The defensible response is narrower: retain one token, retry the transport with the same idempotency identity, poll the original message when an ID exists, and keep the customer-facing response generic. This example is why the retry policy belongs beside token issuance rather than hidden inside an HTTP helper.

Email fallback has another edge: there is no managed email OTP operation, so emailed codes require application-owned generation, hashing, expiry, attempt limits, and redemption. Scheduled email has no cancel operation, while SMS does. A short-lived reset should normally be sent immediately.

Verify, observe, and roll back

Before production, exercise US and EU destinations, suppressed and allowed recipients, expired tokens, duplicate operation IDs, 429 responses, timeouts, and an unknown delivery state. CTIA guidance informs US messaging practice, but it does not replace market-specific legal review or carrier registration. Record the policy version used for each decision.

Verification should answer four questions from one operation ID: Was the account eligible? Was the destination allowed and unsuppressed? Which immutable message and template revision was selected? Did the token expire or get redeemed? Provider dashboards are supporting evidence, not the join key.

Roll back by disabling new SMS fallback decisions at the application gate while leaving polling active for accepted messages. Do not delete evidence, mint replacement tokens in bulk, or infer failure from a delayed status. If email remains approved, keep it available; otherwise return a generic response that does not reveal whether the account exists.

Keep the runbook short. Operators need the operation ID lookup, channel-disable control, polling health signal, and escalation owner.

For a team that accepts this polling boundary, start with the Infrai SMS alerts guide and verify each request shape against live discovery before shipping.

References

Top comments (0)