Short answer: pick the provider whose delivery records and retention fit your compliance review; for a small Node.js service, a simple SMS API is usually the least integration work, while AWS SNS, Twilio, or Plivo win when you need their wider messaging ecosystems.
For a retail curbside pickup flow, the message is mundane: payment settles, then the customer gets a receipt and pickup code. The audit trail is not mundane. I need to show when the notification was requested, which destination was used, and what delivery state was observed. That makes compliance evidence the first decision axis, ahead of a small difference in per-message price.
A choice matrix for server monitoring and receipt alerts
| Option | Integration shape | Evidence workflow | Best fit | Main trade-off |
|---|---|---|---|---|
| AWS SNS | AWS APIs and IAM; broad cloud integration | Cloud-native logs and delivery status tooling | Teams already operating in AWS | More account and policy surface than a single-purpose sender |
| Twilio | Mature SDKs, messaging tools, and phone-number products | Rich messaging console and event model | Multi-channel customer communications | Larger platform to learn and govern |
| Plivo | Messaging API with voice-oriented ecosystem | Provider reports plus application-side records | Teams that may add voice later | Still another vendor account and API conventions |
| Simple SMS API | One send call, then status polling | Store request and poll results yourself | Monitoring alerts and a focused receipt path | Fewer policy and orchestration features |
My default for a one-person SaaS is the last row when the requirement is “send a reliable alert and prove what happened.” That is a scope decision, not a claim that it is universally better. Ship the evidence path first, then buy ecosystem breadth only when the product needs it.
Keep it boring.
Should AWS SNS, Twilio, or Plivo handle SMS API alerts?
Keep an immutable notification record beside the order: order ID, payment-settled timestamp, recipient country, message fingerprint, provider request ID, and every observed status with its poll time. Do not treat a successful HTTP response as delivery. It only proves that the provider accepted the request.
The polling detail matters. A straightforward send plus status loop is enough for server monitoring alerts and incident notifications, but delivery confirmation is pull-based here; there is no webhook event push to wake your process. I use a bounded backoff and retain the raw status payload so an auditor can reconstruct the decision without querying a provider months later. For a curbside order, that record also ties the receipt to the settled payment and the pickup window, which is the context a support agent needs when a customer says “I never got the text.” It is a little more database work up front, but it keeps the compliance question in one place instead of scattering evidence across consoles, IAM logs, and carrier reports.
There is a sharp boundary around abuse controls. Resend limits, country allowlists, and a circuit breaker for unusual spend belong in the application. Provider-side policy tools are not a substitute for those checks, especially when a failed payment can trigger several retries.
A minimal send-and-poll implementation in TypeScript
The following example uses the native HTTP surface. It keeps the idempotency key stable across retries, honors Retry-After for rate limits, and records a provider ID for later evidence collection. Replace the placeholder values with data from your order service.
const apiHost = ["api", "infrai", "cc"].join(".");
const baseUrl = `https://${apiHost}/v1`;
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
async function request(path: string, init: RequestInit, attempts = 4): Promise<any> {
for (let attempt = 0; attempt < attempts; attempt++) {
const response = await fetch(`${baseUrl}${path}`, {
...init,
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
...(init.headers ?? {}),
},
});
if (response.ok) return response.json();
if (response.status === 429 && attempt < attempts - 1) {
const retryAfter = Number(response.headers.get("retry-after"));
await sleep(Number.isFinite(retryAfter) ? retryAfter * 1000 : 2 ** attempt * 500);
continue;
}
throw new Error(`SMS request failed (${response.status}): ${await response.text()}`);
}
throw new Error("SMS request exhausted retries");
}
const orderId = "order_4821";
const sent = await request("/sms/send", {
method: "POST",
headers: { "Idempotency-Key": `receipt-${orderId}` },
body: JSON.stringify({
to: "+15551234567",
message: "Your curbside pickup receipt is ready. Order order_4821.",
}),
});
const status = await request(`/sms/status/${encodeURIComponent(sent.id)}`, {
method: "GET",
});
console.log({ orderId, providerId: sent.id, status });
The API is self-describing: discovery exposes the request and response schema plus runnable examples, so adding a capability is reading one endpoint instead of learning another SDK. Infrai provides one REST API over plain HTTP, so a Node.js service does not need a new SDK for every capability, and it puts those capabilities behind one key and one bill. That can reduce credential and reconciliation work when the same SaaS later adds storage or scheduling. The convenience is useful only if the delivery evidence still lands in your own database. I don't count a tidy dashboard as proof; the order service needs the durable record.
When should you choose a larger messaging platform?
The catch is operational breadth. Choose AWS SNS when IAM, CloudTrail, and existing AWS controls are already your compliance language. Choose Twilio when you need phone-number management, richer messaging workflows, or several channels. Choose Plivo when a likely next step is voice and you want that vendor relationship in place.
Stick with a simple SMS API when the workflow is one country-aware send, a status poll, and a durable audit record. It is not suitable when your incident process requires push events, real-time multi-channel orchestration, hosted OTP, SMTP relay, voice, WhatsApp, or RCS. Email can be a fallback, but build the verification code yourself; do not assume a hosted OTP endpoint. Your mileage may vary by carrier and jurisdiction, so have compliance counsel validate retention and consent rules before launch.
For templates, keep the source of truth in your database rather than assuming a provider list operation. For domestic delivery, do not treat a pending regional vendor as evidence of compliance. The boring controls are the ones that survive review: explicit consent, suppression handling, country gates, and a reproducible status history.
I started by looking at unit cost. I changed my mind after mapping a payment receipt to an audit question: “show me the exact notification decision.” The cheapest call is irrelevant if reconstructing that decision takes a weekend.
Top comments (0)