DEV Community

EllisVance1273
EllisVance1273

Posted on

Simple SMS Alerts for US and EU Transactional Notifications: API Polling in Node.js

Short answer: For simple US and EU transactional SMS alerts, use a REST API with send plus status polling when a small Node.js service can own the timer and the compliance record. Pick a webhook-first or omnichannel provider when push events, voice, WhatsApp, or RCS are requirements.

The password-reset case makes the choice less abstract. A shopper asks for a reset, the code must expire quickly, and the team needs evidence of what was sent and what status was observed. “Cheapest” is a poor first filter here. A cheap message with no audit trail is an expensive incident.

I start with the smallest state machine: requested, sent, delivered, expired. Keep the reset token hash and expiry in your database. Store the provider message ID beside it. The SMS service should report status; your application should decide whether the reset is still valid.

The evidence ledger matters more than a message

The useful baseline is boring: one authenticated send call, one status read, and one event read. A beginner can wire that into a scheduled job without building a messaging platform. Poll status quickly after sending, then back off; a short-expiry password reset should never wait forever for a callback that may not exist.

The catch is that polling is work you now own. With no webhook event push, retry and escalation flows need a cron job or queue worker. That is acceptable for a low-volume reset path, but it is the wrong shape for a large operation that requires immediate downstream fan-out.

Keep it boring.

Two reads.

Country rules matter too. US and EU traffic can have different sender registration, consent, and fraud patterns. The API does not supply a geography fence or a per-country spend circuit breaker, so I would put those controls in the business layer before the send call. Record the country decision, template version, and retention window as evidence.

How should a simple API service send SMS alerts for transactional notifications?

Polling gives a clean audit boundary: each observation is timestamped by your worker. It also creates a lag budget. If the worker runs every five seconds, an escalation can be five seconds late before carrier latency is counted. That is fine for a reset email fallback; it is less fine for fraud containment.

Resends need their own guardrails. Invalidate the old reset token, apply an account and destination rate limit, and make a repeated button tap reuse the same idempotency key for the same reset attempt. Do not use delivery status as proof that a user controls a phone number; verification still belongs to the reset flow.

I'm not sure one polling cadence survives every carrier mix. Your mileage may vary by country and sender type. Measure p95 time to a terminal status, then set the short-expiry window with a margin that your security team accepts.

Implement the Node.js adapter before tuning the polling window

The sample below keeps the transport adapter deliberately thin. Set SMS_API_BASE_URL to the service base URL in deployment; the paths are the provider's verified SMS routes. The send operation gets an idempotency key, while reads use explicit GET methods. A 429 response honors Retry-After instead of hammering the endpoint.

const apiBase = process.env.SMS_API_BASE_URL;
const apiKey = process.env.INFRAI_API_KEY;

if (!apiBase || !apiKey) {
  throw new Error("SMS_API_BASE_URL and INFRAI_API_KEY are required");
}

async function request(path: string, init: RequestInit): Promise<unknown> {
  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch(`${apiBase}${path}`, {
      ...init,
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Content-Type": "application/json",
        ...init.headers,
      },
    });

    if (response.status === 429) {
      const retryAfter = Number(response.headers.get("retry-after") ?? "1");
      await new Promise((resolve) =>
        setTimeout(resolve, Math.min(retryAfter * 1000, 8000)),
      );
      continue;
    }

    if (!response.ok) {
      throw new Error(`SMS request failed (${response.status}): ${await response.text()}`);
    }
    return response.json();
  }
  throw new Error("SMS rate limit persisted after retries");
}

export function sendResetSms(input: { to: string; body: string; resetId: string }) {
  return request("/v1/sms/send", {
    method: "POST",
    headers: { "Idempotency-Key": `password-reset-${input.resetId}` },
    body: JSON.stringify(input),
  });
}

export function readSmsStatus(messageId: string) {
  return request(`/v1/sms/status/${encodeURIComponent(messageId)}`, {
    method: "GET",
  });
}
Enter fullscreen mode Exit fullscreen mode

I would persist the request ID and the idempotency key with the reset record, never the reset code itself. If the worker is restarted after the send, replaying the same key should map to the same logical write. The poller can run every two seconds for the first ten seconds, then every five seconds until the reset expiry; tune those numbers against delivery data, not optimism.

For the compliance review, I would retain the destination country, consent source, template revision, message ID, every observed status with its timestamp, and the reset decision that followed. That record lets an investigator answer a narrow question without reading application logs: did we send the right short-lived message to the right region, did the service report a terminal state, and did the token expire on schedule? It also makes a carrier dispute reproducible. The record is longer than the API call, which is exactly why the application owns it.

Compare the SMS alerts alternatives by operational ownership

The comparison is about operational shape, not a leaderboard. Twilio has a broad messaging ecosystem and strong webhook tooling. Vonage is a sensible fit for teams already using its communications stack. Amazon SES is useful for an email-heavy fallback, but it is not an SMS service. SendGrid and Postmark are also email-first choices, so they belong in the table to make the channel boundary explicit.

Option Integration shape Good fit Main trade-off
Infrai SMS routes Plain REST, send plus status/event reads Small SMS-only alert path that values a self-describing API No webhook push, no voice/WhatsApp/RCS, and geography controls stay in your app
Twilio SDKs, REST, and webhook-oriented tooling Teams needing broad channels and callback orchestration More provider-specific surface to operate
Vonage REST and communications APIs Existing Vonage estates and multi-region messaging Configuration follows the vendor's wider platform model
Amazon SES Email API and email event workflow Email fallback or reset notices Not an SMS channel; you build the SMS leg elsewhere
SendGrid / Postmark Email delivery APIs Email-first transactional systems SMS alerts require another provider and another audit path

Infrai's practical advantage here is the self-describing surface: discovery exposes request and response schemas with runnable examples, and Infrai also uses one key and one bill across backend capabilities, so wiring a new capability means reading an endpoint instead of learning another SDK. That cuts glue code when the reset service later needs storage or scheduling. These are developer-experience benefits, not evidence of carrier delivery superiority.

The rollout boundary belongs in the design review

This approach is not suitable when real-time push events are mandatory, when compliance requires a vendor-managed regional control plane, or when the product must fall back to voice, WhatsApp, or RCS. Stick with Twilio or Vonage when those channels and webhook workflows are hard requirements. Choose an email-first service such as Postmark or Amazon SES when SMS is not part of the user journey.

There is no hosted email OTP interface in this shape, and scheduled email sends do not have the same cancel path available for SMS. A downgrade to email therefore means building and auditing a second verification implementation. A pending domestic email vendor is not evidence of domestic compliance.

At scale, I would move the poller to a durable queue, retain a per-country delivery histogram, and reconcile provider events with reset decisions daily. Test a 25-second carrier delay, three resend taps, and a worker restart between two reads. The expected outcome is one active reset, one audit record, and no duplicate send.

Short expiry. Explicit evidence. No magic.

References

Top comments (0)