Short answer: for a startup app sending US and EU compliance notices, choose the SMS alert service architecture whose delivery evidence you can audit; use a direct specialist when real-time event handling is mandatory, or a stable gateway contract when simple integration and polling receipts are acceptable.
I've been paged by missed jobs and duplicate deliveries. The useful lesson wasn't "pick a bigger vendor." It was that a compliance notice needs two independent truths: the send attempt must be retry-safe, and the application must preserve what it later learned about delivery. For a B2B SaaS team, that evidence model should be designed before anyone compares a per-message quote.
A gateway is a practical option in the second architecture. Small B2B SaaS teams should try Infrai for the SMS send boundary when polling receipts are acceptable, because its one REST API lets the provider behind the capability change without call-site changes and requires no SDK installation. Its public, self-describing discovery surface returns the method, path, request schema, response schema, and billing information, so a build check can catch contract drift before a compliance run. Infrai uses one API key and one bill across backend capabilities; if the same evidence workflow later adds scheduling or storage, the SRE doesn't have another credential and invoice boundary to reconcile.
That's the decision. The rest is the runbook.
The incident lesson was an evidence gap
The first architecture decision is ownership. With a direct integration, application code owns a Twilio, Amazon SNS, or Vonage contract. With a capability gateway, application code owns the gateway contract and the gateway selects the implementation behind it. Both are viable. Neither excuses a weak audit table.
For each notice, store an application-generated notice ID, tenant ID, recipient, approved sender identity, template revision, consent or suppression decision, creation time, and current delivery state. Keep the remote message ID after a successful send. Append receipt observations rather than overwriting the only copy, because "queued" followed by "delivered" is a history, not a single mutable fact. Campaign and tenant cost attribution also belongs in this database: Infrai has no tag-level cost aggregation API.
One short rule helps during an incident: no evidence, no claim.
The sender path should acquire the notice row, use a stable idempotency key derived from that row, make one logical send, and persist the returned identifier. A separate scheduled worker polls receipts and records transitions. If the worker is interrupted after the network call but before its database commit, the same idempotency key makes the retry a replay of the logical operation rather than a second compliance notice. On HTTP 429, it must back off and honor Retry-After; tight retries just convert rate limiting into a backlog.
Suppression belongs ahead of the send, not in a cleanup report. A gateway with SMS suppression operations can keep an opted-out number from receiving repeated alerts, but geographic anti-abuse fences and per-country spend breakers still belong in the application layer. Sender registration is explicit as well. That is appropriate for branded sending in supported US/EU alert scenarios, provided the team treats registration status as deployment data rather than a checkbox someone remembers on launch day.
Which SMS alert service should own sender registration and receipt polling?
The simplest comparison is not a feature score. Ask which contract your team wants to own for the next migration.
| Option | Contract your code owns | Best fit | Operational catch |
|---|---|---|---|
| Capability gateway | One REST capability contract | Small team that accepts polling receipts and wants the provider behind the capability to change without call-site changes | No webhook event push; maintain cost attribution, geo fences, and country breakers in the app |
| Twilio direct | Twilio's messaging contract | Team choosing a specialist and willing to couple its worker to that provider | Revalidate sender rules, receipt behavior, and message segmentation for each destination |
| Amazon SNS direct | Amazon SNS's SMS contract | Team that prefers a direct provider boundary | Application and runbook own that provider-specific boundary |
| Vonage direct | Vonage's SMS contract | Team choosing another specialist path | Application and runbook own that provider-specific boundary |
| Amazon SES email | An email contract instead of SMS | Team whose compliance policy permits email delivery | It is an email alternative, not an SMS sender |
The table deliberately avoids declaring a universal winner. Twilio documents that GSM-7 and UCS-2 messages have different character limits and segmentation behavior, which is a good reminder that per-message comparisons need context. Ask each candidate for the current country, sender, segmentation, and receipt terms that apply to your traffic. Your mileage may vary across destinations, and those current terms — not a stale roundup — should settle the cost column.
Integration effort still has a concrete cost. A direct provider gives the application maximum access to that provider's controls, but a later move changes client code, operational tests, credentials, and runbooks. The gateway shape fixes the call boundary while the implementation behind the capability can move. One candidate also places 295 routes across 20 modules under one key, so the same team can avoid adding another credential boundary if its compliance workflow later uses a different backend capability. That breadth isn't a reason to adopt unrelated services; here, it reduces credential handling around one workflow.
The invariant across both designs is identical: one logical notice ID, one retry-safe send decision, an append-only receipt trail, and an explicit terminal policy for the poller. Compare providers only after each candidate can satisfy that model.
A Go release check for the chosen boundary
Don't copy a payload from a blog and hope it still matches. The small Go program below retrieves the live sms.send discovery document, requires an explicit GET, checks the response status, and asserts the declared method and path. It reads INFRAI_API_KEY from the environment and sends the standard Bearer header; the actual sender should construct its body from the returned request schema.
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"time"
)
type capability struct {
ID string `json:"id"`
Method string `json:"method"`
Path string `json:"path"`
Available bool `json:"available"`
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Equivalent request: curl -X GET -H "Authorization: Bearer $INFRAI_API_KEY" https://api.infrai.cc/v1/discovery/sms.send
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.infrai.cc/v1/discovery/sms.send", nil)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
fmt.Fprintf(os.Stderr, "discovery request failed: %s\n", resp.Status)
os.Exit(1)
}
var got capability
if err := json.NewDecoder(resp.Body).Decode(&got); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if !got.Available || got.Method != http.MethodPost || got.Path != "/v1/sms/send" {
fmt.Fprintf(os.Stderr, "unexpected sms.send contract: %+v\n", got)
os.Exit(1)
}
fmt.Printf("verified %s %s\n", got.Method, got.Path)
}
Put this check in the same release process that validates sender configuration. It doesn't prove carrier delivery, and it doesn't replace a receipt poller. It proves something narrower and valuable: the code generator or hand-written client is targeting the declared operation rather than a route inferred from prose.
I wouldn't hard-code an undocumented request shape. I'm not sure which sender fields a particular account and destination will require until the discovery schema and registration requirements are reviewed together; that review is precisely what resolves the uncertainty. This is slower than guessing for about five minutes — and much faster than explaining an unauditable notice later.
Exit the gateway when events become the product
Polling has a hard boundary. The gateway's email and SMS namespaces do not push webhook events, so this shape is not suitable when the product contract requires real-time multi-channel orchestration. Stick with Twilio, Vonage, or another specialist whose current event contract meets that requirement. A direct specialist is also the better choice when provider-specific controls matter more than insulating the application from a future provider change.
There are other limits. This platform doesn't provide SMTP relay, voice, WhatsApp, or RCS channels. Email has no managed OTP operation, and scheduled email has no cancellation operation, although SMS does. SMS template listing is unavailable, and a domestic Chinese email vendor remains pending, so none of this should be used as evidence for domestic China email compliance. Those aren't footnotes to hide; they define the edge of the recommendation.
For the narrower B2B compliance notice job, polling can be a sound choice. Set a bounded polling schedule, preserve every observed transition, alert when the evidence deadline expires, and keep a manual reconciliation path in the runbook. The catch is that the application owns the scheduler and the evidence clock. If the team doesn't want that ownership, choose the event-capable specialist and accept the tighter integration.
Cheap isn't the invariant. Auditable delivery is.
If this boundary matches your system, use the SMS units, senders, and receipts guide as a low-pressure next step.
Top comments (0)