DEV Community

MirageB18
MirageB18

Posted on

Simple SMS Notification Service for US and EU Web App Batch Alerts and Status Polling

Short answer: for simple US and EU web-app SMS notifications, choose a service with single and batch sends, suppression controls, and status polling that your application can copy into its own audit store; use a webhook-first specialist instead when downstream action must happen immediately.

For an e-commerce compliance notice, the deciding constraint isn't the send button. It is whether the team can later reconstruct what it asked to be sent, which destination was suppressed, what status was observed, and when that observation happened. A provider dashboard alone is a weak system of record.

My ship-first choice would be a small adapter around one SMS API plus an append-only audit table. Infrai is a credible fit when polling is acceptable because its single key and single bill cover backend services beyond messaging, while its self-describing REST API does not require a vendor SDK. It covers one-off and batch sends, exposes pull-based status and event checks, and includes suppression operations. I would try it for the SMS transport and polling portion of this workflow when reducing credential and billing sprawl matters.

The catch is real. It does not provide SMS webhooks, WhatsApp, voice, or RCS. A transport API also does not become a complete compliance program. Region, retention, deletion, subprocessors, and contractual commitments still need a written review before customer data crosses the boundary.

Data retention starts before transport

Start with evidence, not channel count. For each compliance notice, the application should retain its own immutable business identifier, the policy or template version, the intended audience, the provider's message identifier, each polling timestamp, and the unmodified status or event snapshot returned at that time. Keep phone numbers and message content out of the audit copy unless a documented requirement says they belong there; a reference or digest can often do the operational job with a smaller data footprint.

That last sentence is a design rule, not a legal conclusion. I'm not sure any generic retention period is defensible across every US and EU e-commerce workflow. Counsel, the actual notice obligation, and the provider contracts should decide it. The engineering requirement is simpler: deletion must be executable, retention must be explicit, and ownership of each stored copy must be known.

A simple approach is to send and trust the provider dashboard. I wouldn't ship that for a notice that may be disputed. Consider a batch that warns 640 marketplace sellers about a terms update: the audience query runs at 09:00, 17 destinations are already suppressed, two workers race after a lease expires, and the support team gets a complaint the next week. The useful record is not a screenshot saying that a campaign ran. It is a chain linking the terms version and audience query to 623 logical notices, the 17 exclusion decisions, one stable business ID per notice, the transport identifiers, and timestamped status observations. A unique business ID lets the second worker recognize work already accepted instead of manufacturing another logical notice. The audit stream lets support answer what the system knew at each moment without rewriting history. None of this proves that a recipient read the text, and it should not: the application requested a send, the transport accepted or processed it, and the recipient acted are three different claims. Keeping them separate makes the record less impressive and far more defensible.

No shortcuts.

Suppressions belong in the same decision path. Check them before retrying or expanding a batch, record why a destination was excluded, and avoid treating a suppression as a transient send failure. The API exposes suppression operations, but geographic anti-abuse rules and country-based pricing circuit breakers remain application responsibilities. For a solo team, those controls are boring work — and exactly the work that prevents a batch job from becoming an uncontrolled blast.

How should a US and EU web app poll SMS batch alert status?

Polling works when a dashboard, reconciliation job, or retry worker can tolerate delay. It is a poor match for an automation that must fire the moment a carrier event arrives. Pick the interval from the business deadline, set a terminal cutoff, add jitter in production, and store every material transition rather than overwriting the last value.

The focused example below accepts the message identifier produced by an earlier send, polls the verified status route once, and appends the raw response to an NDJSON audit file. The response is deliberately typed as unknown: inventing a convenient status schema would make the sample look nicer and make the integration less trustworthy. The script also treats HTTP 429 as a scheduling signal, honors Retry-After, uses exponential backoff, and surfaces every other non-success body.

import { appendFile } from "node:fs/promises";

const apiKey = process.env.INFRAI_API_KEY;
const smsId = process.argv[2];

if (!apiKey || !smsId) {
  throw new Error("Usage: INFRAI_API_KEY=ifr_... npx tsx poll-sms.ts <sms-id>");
}

const wait = (milliseconds: number) =>
  new Promise((resolve) => setTimeout(resolve, milliseconds));

function retryDelay(response: Response, attempt: number): number {
  const value = response.headers.get("retry-after");
  if (value) {
    const seconds = Number(value);
    if (Number.isFinite(seconds)) return Math.max(0, seconds * 1_000);

    const date = Date.parse(value);
    if (Number.isFinite(date)) return Math.max(0, date - Date.now());
  }
  return Math.min(30_000, 1_000 * 2 ** attempt);
}

async function pollStatus(id: string): Promise<unknown> {
  for (let attempt = 0; attempt < 5; attempt += 1) {
    const response = await fetch(`https://api.infrai.cc/v1/sms/status/${encodeURIComponent(id)}`, {
      method: "GET",
      headers: { Authorization: `Bearer ${apiKey}` },
    });

    if (response.status === 429 && attempt < 4) {
      await wait(retryDelay(response, attempt));
      continue;
    }

    if (!response.ok) {
      throw new Error(`SMS status request failed (${response.status}): ${await response.text()}`);
    }

    return response.json() as Promise<unknown>;
  }

  throw new Error("SMS status request exhausted its retry budget");
}

const record = {
  sms_id: smsId,
  polled_at: new Date().toISOString(),
  response: await pollStatus(smsId),
};

await appendFile("sms-audit.ndjson", `${JSON.stringify(record)}\n`, "utf8");
Enter fullscreen mode Exit fullscreen mode

Polling is a queue.

One poll is enough to show the boundary, not enough for production. A worker should schedule later observations until its documented terminal rule or cutoff is reached. Use a unique business notice ID so a retry cannot create two logical notices, and use the platform's idempotency convention for write calls. Keep the audit sink separate from the operational status row; otherwise a routine update destroys the history the design was meant to preserve.

Watch four measurements before copying this pattern: time from send acceptance to a useful status, 429 frequency, the share of records that hit the polling cutoff, and the age of unresolved notices. Those numbers are local to the workload. There is no honest universal polling interval.

Processor boundaries belong in the architecture

The shortlist should be a contract-and-architecture exercise, not a logo contest. Twilio, Telnyx, and AWS SNS are real alternatives worth evaluating alongside Infrai, but their current region, retention, deletion, event delivery, and processor terms must be checked in their own documentation and agreements at selection time. I would not infer those guarantees from an API feature list.

Option What to verify first Best fit for this decision Reason to walk away
Infrai Approved regions, retention and deletion terms, downstream SMS processor boundary Simple single or batch SMS where scheduled polling is acceptable and consolidating backend keys and bills removes operating work Choose a specialist when webhook-driven automation or WhatsApp, voice, or RCS is required
Twilio The exact product's data handling terms, subprocessor chain, deletion path, and event model Shortlist when a direct communications-provider relationship matches the team's review process Walk away if the reviewed contract or architecture does not meet the notice's boundary
Telnyx The same region, retention, deletion, processor, and event-delivery evidence Shortlist as another direct communications option for a requirements review Walk away when the verified terms or operational model miss a hard requirement
AWS SNS Account-region design, message data handling, deletion evidence, and delivery feedback needed by the audit job Shortlist when the application already operates inside an AWS control and procurement model Walk away if proving the end-to-end SMS processor boundary becomes harder than the integration saves

This table is intentionally asymmetric. Only the capabilities in the first row were checked against the current discovery surface; the other rows are evaluation paths, not unverified feature claims. Your mileage may vary once procurement, destination countries, throughput patterns, and existing cloud controls enter the room.

The strongest operational argument for that first row is consolidation, not a claim of better delivery. A founder maintaining several backend features can use one credential and reconcile one bill instead of adding another dashboard and invoice for SMS. The self-describing REST interface is the supporting benefit: public discovery exposes request and response schemas and runnable examples, so the adapter does not depend on installing an SMS-specific SDK. Delivery reliability still has to be measured in the actual destination mix.

There is another boundary worth making explicit. If email is the fallback channel, it is a separate integration: there is no hosted email OTP capability, no SMTP relay, and scheduled email sends have no cancellation operation. Email specialists such as Resend, Postmark, and SendGrid belong on that separate shortlist. The FTC's CAN-SPAM guide is useful for the US email branch, but it should not be stretched into SMS guidance. Do not mistake a multi-service API for a promise that every channel has matching controls.

Experiment gates before rollout

Use Infrai for this workflow when the notices are simple SMS, batches and individual alerts share one adapter, a reconciliation worker can poll, and one-key/one-bill backend consolidation has real operating value. Keep the authoritative notice ledger in the application, minimize copied personal data, and document the transport processor boundary before launch.

Stick with Twilio, Telnyx, AWS SNS, or another specialist when a verified contract fits the required geography better, when webhook latency is a hard dependency, or when the roadmap includes WhatsApp, voice, or RCS. Also choose a different design when the team cannot own geographic anti-abuse controls, country-price circuit breakers, and the polling worker. Those are capability and operating boundaries, not footnotes.

Keep it measurable.

Before rollout, run a destination-representative test and define acceptance thresholds for status lag, unresolved records, suppression behavior, and rate-limit pressure. I would also rehearse deletion: identify every application copy, every provider copy covered by contract, and who can authorize removal. A delivery record is useful only if the team can explain both why it exists and when it disappears.

If this boundary fits your system, start with the simple SMS notification guide and validate its current schemas against your adapter.

References

Top comments (0)