For a marketplace web app, the least complex SMS design that still survives a compliance review is a queued, batch-aware sender with a small polling ledger and explicit suppression records. It does not need a webhook to prove what happened. It needs an immutable event id, a decision record for consent, and enough delivery state to explain each attempt.
Short answer: choose a service that can send US and EU messages in batches, expose status for polling, and return suppression reasons; keep the evidence in your own system and treat provider logs as supporting data.
Start with the evidence, not the message
The business event is narrow: a seller gets a text when a new order is accepted. The audit question is wider. Who authorized that destination? Which policy version allowed the message? What exact content was rendered, and when did the sender hand it to the carrier?
I model one notification as an evidence bundle. The bundle contains the marketplace order id, seller id, phone-number hash, consent source and timestamp, template version, locale, batch id, provider message id, and every observed status transition. Store the raw phone number only where access controls and retention rules justify it. A hash is useful for joins, but it is not magically anonymous.
That distinction matters in the EU, where GDPR principles include data minimization and storage limitation. In the US, the FTC's CAN-SPAM guidance is written for commercial email, not a blanket SMS rule, but its ideas about truthful identification, opt-out handling, and accountable senders are still useful review prompts. Your legal interpretation may differ; have counsel map the exact messaging program to applicable rules.
What should a simple SMS service record for US/EU batch alerts and polling status?
Separate four clocks: order acceptance, enqueue time, provider submission, and the last status observation. A batch can contain 500 seller notifications while each message retains an individual id. Polling then asks for changes after a cursor, rather than downloading the same history every minute.
For example, an internal record can look like this:
| Field | Why it exists | Retention decision |
|---|---|---|
event_id and order_id
|
Deduplication and dispute lookup | Keep for the audit window |
consent_version, source, timestamp |
Shows why sending was permitted | Keep until policy requires deletion |
template_version and rendered hash |
Reconstructs the approved content | Keep; avoid storing message text by default |
batch_id, provider id |
Connects one job to many attempts | Keep through reconciliation |
status, observed_at, reason |
Explains delivery and suppression | Keep summarized history |
The sender should be idempotent: the same event_id and template version produce one logical notification, even if a worker retries. A suppression is a business outcome, not a transport failure. Record it as suppressed, preserve the reason category, and prevent automatic retries until a new consent event exists.
Keep the key stable.
Polling has an operational cost. If 20,000 messages are checked every 30 seconds, most responses contain no change. Use exponential backoff for quiet batches, a bounded lookback for late carrier updates, and a dead-letter path for records that never reach a terminal state. I prefer a seven-day reconciliation window, but that is a policy choice, not a universal fact.
How do you compare no-webhook SMS workflows without losing compliance proof?
Compare workflows by the evidence they leave behind, not by whether a dashboard says “delivered.” A no-webhook design is reasonable when your worker can poll a documented status endpoint, persist cursors durably, and alert when the age of the oldest unresolved message exceeds your service-level objective.
The failure modes are predictable. A batch retry can duplicate a seller alert if idempotency is keyed only by phone number. A suppression list can be applied after enqueue, creating a message that was technically accepted but should never have left your system. A clock mismatch can make an EU quiet-hours rule appear compliant when the timestamp was recorded in server time. Tests should inject each condition and assert the evidence bundle, not merely the HTTP response. For a concrete test, enqueue two records for order ord_1842, advance the polling cursor between responses, then replay the first response with a different server clock; the expected result is one logical send, one cursor advance, and an audit note that preserves both observed timestamps. If the test cannot explain that sequence from stored fields alone, the design is missing evidence.
The interface contract should also be boring: authenticated HTTPS, documented pagination, stable status values, and a reason field for suppression. Keep provider adapters behind one internal boundary so a change in carrier coverage does not alter order processing. Three words describe the runbook: enqueue, observe, reconcile.
Retention is a product decision
The largest observability bill is usually self-inflicted: verbose payloads copied into logs on every retry. Log ids, counts, latency, and reason codes; sample message bodies out of routine traces. A one-line structured event is easier to search and cheaper to retain than a serialized request with phone numbers and order metadata.
A common investigation starts with a dashboard filter that excludes suppressed; the sender is correct, but the evidence looks like a loss. I don't treat a green delivery rate as proof of compliance. Suppression counts sit beside sent and delivered counts, with a drill-down to the consent version. Small change. Big difference.
Keep hot operational data for the period your support team needs, then aggregate. The trade-off is real: deleting rendered content protects privacy and reduces storage, but it limits what you can show a seller during a content dispute. Retain a content hash and template revision when the full text is no longer justified.
A practical selection rule
Select the simplest service that meets these tests in a staging account: US and EU destination coverage for your actual sender identity, batch submission with per-message ids, pollable status history, explicit suppression responses, and exportable timestamps. Verify opt-out behavior with a controlled number and document who owns the suppression source of truth.
Do not choose a polling-only workflow when your delivery objective is sub-second or when carrier status must trigger immediate compensation; a push event stream may be more suitable. Stick with a webhook-capable design when the provider's polling history is too shallow for your audit window. Conversely, a webhook is unnecessary ceremony for a low-volume marketplace that already runs a reliable reconciliation worker.
The conclusion is intentionally modest: compliance evidence comes from your data model and retention policy. A transport service can supply identifiers and statuses, but it cannot decide whether a seller consented. I'm not sure any provider can infer that business decision from a phone number alone. Build it into the order-to-message boundary, and the vendor choice becomes a bounded engineering trade-off.
Top comments (0)