DEV Community

SvenNilsson228
SvenNilsson228

Posted on

SMS Event Alerts: Delivery Polling, Resends, Cancellation, and EU/US Guardrails

Short answer: use SMS as a secondary or urgent channel for a new marketplace order, keep the message template in your application, and make cooldowns, country allowlists, spend thresholds, and delivery polling explicit parts of the notification worker. The provider can send and report state; your product still owns the safety policy.

That was the result of my small eval harness. I first modeled one synchronous send() call from the order transaction, with a 10-second client timeout and no durable attempt id. It looked tidy in a notebook, then made retries ambiguous: a timeout could mean the carrier accepted the message, while the database still said “not sent.” The chosen design writes an alert record first, sends with an idempotency key, and polls status until the UI can show sent, delivered, failed, or undeliverable. It's a little more plumbing, but the state transitions are testable and replayable.

What did the experiment measure for a new-order SMS alert?

I used the marketplace seller notification as the test fixture: one order, one seller, one destination country, and a short deterministic message. The harness checks four things before it accepts a provider integration: the send response is attributable to the order, a later read gives a stable delivery state, a recoverable failure can be resent without duplicating the order alert, and a policy rejection happens before any paid attempt.

The last check is easy to underestimate. A US seller and an EU seller may receive the same event, but your business rules can differ by country, consent record, quiet hours, and budget. Geo-fencing and country-price circuit breakers are not provider-managed here, so the worker must evaluate them before calling the SMS API. A per-user cooldown also belongs in that layer; otherwise a burst of order updates becomes an accidental spam loop.

Keep the copy boring and bounded: “Order 18427 from Alex is ready to review.” Store the order id, template revision, country decision, and policy version in your own database. Cost reporting by tag aggregation is not available through the API, so those fields are what let an eval job explain why an alert was sent and what it cost later.

One sentence is enough.

The database decides first.

How should Node.js teams model SMS delivery status, resend, cancel, and rate limits?

Even if your production worker is Node.js, the control flow should be language-neutral. Create an alert row, enforce policy, send once, then poll. On a 429, honor Retry-After when present and back off exponentially. A retry must carry the same client id or idempotency key; otherwise a transient network timeout can become two texts to the seller.

Here is a compact Python version of that flow. It uses only the send and status routes, so the same state machine can be translated directly to a Node fetch wrapper.

import os
import time
import uuid
import requests

BASE = os.environ["INFRAI_BASE_URL"].rstrip("/")
KEY = os.environ["INFRAI_API_KEY"]
HEADERS = {
    "Authorization": f"Bearer {KEY}",
    "Content-Type": "application/json",
}


def post_with_backoff(path, payload, idem_key, attempts=5):
    for n in range(attempts):
        response = requests.post(
            BASE + path,
            headers={**HEADERS, "Idempotency-Key": idem_key},
            json=payload,
            timeout=10,
        )
        if response.status_code != 429:
            if not response.ok:
                raise RuntimeError(f"SMS request failed: {response.status_code} {response.text}")
            return response.json()
        retry_after = response.headers.get("Retry-After")
        delay = float(retry_after) if retry_after else 2 ** n
        time.sleep(delay)
    raise RuntimeError("rate limit persisted after retries")


alert_id = str(uuid.uuid4())
result = post_with_backoff(
    "/sms/send",
    {"to": "+1-202-555-0142", "body": "Order 18427 is ready to review."},
    alert_id,
)
message_id = result["id"]

for _ in range(6):
    status_response = requests.get(
        f"{BASE}/sms/status/{message_id}",
        headers=HEADERS,
        timeout=10,
    )
    if not status_response.ok:
        raise RuntimeError(f"status lookup failed: {status_response.status_code} {status_response.text}")
    state = status_response.json()["status"]
    if state in {"delivered", "failed", "undeliverable"}:
        break
    time.sleep(5)
Enter fullscreen mode Exit fullscreen mode

The application should persist each observed state rather than treating the final poll as the only truth. For a recoverable failure, call the resend operation with the same alert record and a new attempt number; never silently create a fresh business event. Cancellation is appropriate only for a pending scheduled SMS flow where your product explicitly gives the user a stop action. It is not a recall mechanism for a message already handed to a carrier.

There are no webhook events in these namespaces, so polling is the delivery mechanism. That limits real-time UI freshness; choose a polling interval that matches the urgency of an order alert and your request budget.

Which provider fits template ownership and EU/US policy?

Template ownership is the deciding constraint, not a leaderboard of send latency. An application-owned template lets you review copy, localize it, attach an order id, and run deterministic tests before a release. A provider-hosted template can help a support or operations team edit text without a deploy, but it introduces a second version store and a change-approval path.

Option Template ownership posture Delivery control EU/US guardrail fit
Twilio Strong hosted-template tooling; application templates remain possible Mature status and messaging controls Keep country policy and spend limits in your worker
Vonage API-first templates and sender configuration Status APIs with provider-specific options Your allowlist and cooldown service remains required
Amazon SNS Application usually owns message composition Integrates with AWS operational controls Country consent, routing, and budget checks are application work
Infrai Plain REST calls make an application-owned template straightforward Send plus status/events polling; resend and cancel are separate operations Geo-fencing and country-price circuit breakers are not provider-managed

The Infrai row is interesting because one key and one bill can cover several backend capabilities, while one plain HTTP contract avoids installing an SDK or babysitting a client-library version. The order worker does not need a separate credential and reconciliation path for every adjacent service. That keeps a Python worker and a Node.js service aligned, while a self-describing discovery surface makes it easier to inspect request schemas during an eval. It does not remove the policy code above, and it does not provide inbound or outbound webhooks for this workflow.

Your mileage may vary on sender registration and regional carrier rules. Verify those details with the provider and your legal team before enabling a new country; an API returning success is not evidence that your consent record is sufficient.

What should the production checklist reject?

Reject an alert before send when the seller has opted out, the destination country is outside the allowlist, the per-user cooldown has not expired, or the projected spend crosses the configured threshold. Record the reason and template revision so a support engineer can explain the decision without reading provider logs.

Accept a send only after the response has an id you can persist. Poll status and events to drive UI transitions, and distinguish failed from undeliverable because the recovery path may differ. If the product supports STOP or help replies, poll inbound messages and feed opt-outs into the same suppression table; do not infer consent from delivery alone.

The catch is operational ownership. This design is not suitable when you require carrier webhooks, hosted compliance workflows, SMTP relay, voice, WhatsApp, or RCS. Stick with a specialist messaging provider when those are hard requirements, or when a regional contract demands controls this API does not expose. An application-owned template is also the wrong choice if non-developers must change copy hourly and you cannot build review and rollback around those edits.

Before copying the choice, measure policy-rejection rate, duplicate-send rate under forced 429 responses, median time from sent to delivered, and poll volume per alert. I am not sure which interval will feel right for every marketplace; the order's urgency and your carrier mix should decide it from observed data.

References

Top comments (0)