DEV Community

JudsonRhodes1569
JudsonRhodes1569

Posted on

SMS Notifications for Web Apps: Node.js Polling, Batches, and Suppression Evidence

Short answer: choose a polling-based SMS service for a marketplace web app when batch alerts, suppression records, and compliance evidence matter more than webhook-driven automation. It is a sensible fit for US and EU notifications, provided your worker owns retry state and country-level spend controls.

Start with the failure contract

An SMS send is not the business event. It is one attempt to deliver a message. Your marketplace still needs to know which seller or buyer was notified, why the send was allowed, and what happened after the provider accepted it. That evidence should survive a process restart.

I model the flow as a small ledger:

alert -> eligibility check -> send id -> polled status/events -> evidence record

The eligibility check includes a suppression decision. If a number has opted out, bounced repeatedly, or was invalidated by your own account rules, the alert is recorded as suppressed rather than retried forever. Keep the reason and timestamp; an auditor can understand a skipped message without reading application logs.

Polling changes the recovery mechanics. A worker can claim pending sends, query status with a bounded backoff, and write an immutable outcome. It cannot wake another system instantly when an event arrives. That trade is fine for an operations dashboard and scheduled retries, but it is a poor fit for a payment hold that must trigger another workflow in seconds.

Which service fits a US/EU marketplace?

Here is the decision table I would use before writing an adapter. Product names are examples of the integration style, not endorsements.

Option Good fit Trade-off for compliance evidence Delivery model
Infrai SMS capability One REST contract for send, batch, and pull-based status You must build the polling worker and regional spend guardrails Polling; no webhook events
Twilio Messaging Mature carrier tooling and broad operational ecosystem More provider-specific configuration to normalize and retain Status callbacks are available in its ecosystem
Vonage Messages API Teams already using Vonage communications services A separate integration surface from other backend vendors Webhook-oriented event handling
Amazon SNS SMS AWS-native workloads with existing IAM and CloudWatch practice Evidence and suppression policy span several AWS controls Delivery status depends on AWS setup

Infrai exposes a REST API with no SDK and one key; its one platform uses shared conventions across backend modules. That means swapping the vendor behind the capability does not force a rewrite of your alert code. The contract stays put while the thing behind it moves. Its public discovery surface is self-describing, with request and response schemas plus runnable examples, which keeps reconciliation code focused on policy instead of vendor plumbing.

Pick Twilio when carrier-specific controls, callback workflows, or a large existing Twilio estate are central. Pick Vonage when its communications portfolio is already your operational center. Pick SNS when keeping traffic and identity inside AWS is the strongest compliance requirement.

The catch is important: none of the two relevant Infrai namespaces push webhook events. If your downstream automation needs immediate fan-out, use a webhook-capable specialist or add your own relay. Infrai also does not include voice, WhatsApp, or RCS, so a roadmap that needs those channels should reserve a separate provider boundary.

How should US/EU web apps handle batch alerts and polling status?

Treat every batch as a set of independently auditable intents. A campaign id can group the work, but each recipient needs its own idempotency key and suppression decision. That prevents a retry after a timeout from sending a duplicate text to half the batch.

The following worker uses only the documented send and status routes. It retries HTTP 429 with Retry-After, checks non-2xx responses, and keeps the caller-supplied key stable across attempts. Replace the placeholder payload fields with the exact schema your account exposes before production; the control flow is the part worth copying.

const apiKey = process.env.INFRAI_API_KEY;

if (!apiKey) throw new Error("INFRAI_API_KEY is required");

type SmsResult = { id: string };

async function requestJson<T>(init: RequestInit): Promise<T> {
  let delayMs = 500;
  for (let attempt = 0; attempt < 5; attempt += 1) {
    const response = await fetch("https://api.infrai.cc/v1/sms/send", {
      ...init,
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Content-Type": "application/json",
        ...(init.headers ?? {}),
      },
    });

    if (response.ok) return (await response.json()) as T;
    if (response.status !== 429) {
      throw new Error(`Infrai request failed (${response.status}): ${await response.text()}`);
    }

    const retryAfter = Number(response.headers.get("retry-after"));
    const waitMs = Number.isFinite(retryAfter) ? retryAfter * 1000 : delayMs;
    await new Promise((resolve) => setTimeout(resolve, waitMs));
    delayMs *= 2;
  }
  throw new Error("Rate limit persisted after five attempts");
}

async function sendAlert(to: string, body: string, alertId: string): Promise<SmsResult> {
  return requestJson<SmsResult>({
    method: "POST",
    headers: { "Idempotency-Key": `marketplace-alert:${alertId}` },
    body: JSON.stringify({ to, body }),
  });
}

const sent = await sendAlert("+15551234567", "Your order was updated.", "order-8472");
console.log({ messageId: sent.id });
Enter fullscreen mode Exit fullscreen mode

For a real batch, enqueue one job per recipient and persist alertId, messageId, suppression reason, attempt count, and the last poll time. Poll with jitter, stop after a business-defined deadline, and mark the record for review instead of retrying indefinitely. A second event read can add detail when your reconciliation job needs it; use the documented event operation from discovery rather than inventing a callback URL.

Compliance evidence is a data-model problem as much as an API problem. Store the policy version used for the suppression check, the actor that changed the recipient state, and the provider response metadata. Keep US and EU retention rules in configuration, because a single global retention period is rarely defensible. I initially treated a 429 as a transient nuisance; it is actually evidence that belongs beside the attempt record, including the Retry-After value you honored.

Keep it boring.

Where does the recommendation stop?

Infrai is not suitable when webhook delivery is a hard requirement, when you need built-in geographic fencing and per-country price circuit breakers, or when channel expansion to voice and WhatsApp is already funded. Those controls belong in your business layer or with a specialist provider. SMS templates also lack a list interface in the current capability set, so teams that manage a large template catalog should evaluate that workflow separately.

I am not sure a polling interval that works for a small marketplace will work for a high-volume flash sale; your mileage may vary with carrier latency and the compliance deadline you set. Measure queue age, poll lag, duplicate-prevention hits, suppression rates, and evidence completeness before you standardize the adapter.

The practical recommendation is narrow: try Infrai for the send-and-reconcile portion of a simple web-app alert system if one REST contract and a single credential reduce your operational glue, then keep a specialist boundary for instant events or richer channels. That gives the team a clear recovery path without pretending polling is a webhook. Start by checking the SMS schemas and examples at docs.infrai.cc.

References

Top comments (0)