Short answer: choose an SMS alerts provider only after you can prove consent, suppression, and delivery recovery for every appointment reminder, shipping alert, and account activity event in both US and EU traffic. For a B2B SaaS order receipt sent after payment settles, the durable design is a provider-neutral outbox with idempotency, regional policy checks, and an auditable suppression ledger.
Start with a consent ledger, not a vendor shortlist
At 3am I don't trust a green delivery dashboard. I've been woken by alerts that meant nothing and missed the one that mattered, so I ask which page fired, which event produced it, and whether the recipient had opted out ten minutes earlier. A receipt sender can be technically available while still violating a quiet-hours rule, replaying a duplicate message, or sending a US-style consent record to an EU number whose lawful basis is different.
The failure pattern is predictable: payment settles, the application emits order.paid, a worker calls an SMS API, and a timeout causes the worker to retry. Without a stable message key, the customer receives two receipts. Without a suppression check immediately before dispatch, an opt-out races the queue. Without a durable provider response, the team cannot tell whether to retry, reconcile, or page someone.
That is why provider selection belongs after the event contract. A simple REST API is useful, but it does not define your retry semantics, consent evidence, or incident ownership.
How should an SMS alerts provider handle appointment reminders and shipping events?
Treat each message as a policy decision attached to an immutable event. Store recipient region, purpose, consent source, consent timestamp, locale, and a suppression state. Appointment reminders and shipping alerts are operational messages; account activity messages may be security-sensitive. An order receipt is transactional, yet your policy still needs to say whether a user can suppress it or only change its channel.
The dispatch path should be boring and inspectable:
- Validate the event and derive a deterministic idempotency key such as
receipt:{order_id}:v1. - Read the latest suppression ledger entry; fail closed when the policy requires it.
- Render a versioned template with bounded fields and a region-specific sender identity.
- Enqueue an outbox record before making the network call.
- Record the provider request, response class, and delivery callback against that record.
Here is the core boundary in Go. It deliberately knows nothing about a vendor's SDK.
package notify
import (
"context"
"errors"
)
var ErrSuppressed = errors.New("recipient is suppressed")
type Message struct {
Key string
To string
Purpose string
Template string
}
type Gateway interface {
Send(ctx context.Context, message Message) (string, error)
}
type Ledger interface {
Suppressed(ctx context.Context, number, purpose string) (bool, error)
Reserve(ctx context.Context, key string, message Message) (bool, error)
}
func Dispatch(ctx context.Context, ledger Ledger, gateway Gateway, message Message) error {
suppressed, err := ledger.Suppressed(ctx, message.To, message.Purpose)
if err != nil {
return err
}
if suppressed {
return ErrSuppressed
}
reserved, err := ledger.Reserve(ctx, message.Key, message)
if err != nil {
return err
}
if !reserved {
return nil // another worker owns this idempotency key
}
_, err = gateway.Send(ctx, message)
return err
}
The reserve operation must be atomic, and its record needs an expiry or a reconciliation state. A network timeout is not proof of non-delivery. Mark it unknown, query the provider's status mechanism, and retry only according to an explicit duplicate-risk policy.
Can an SMS alerts provider prove delivery for appointment reminders and shipping events?
Ask for testable answers, not screenshots. Can the service accept a plain HTTPS request from Go, and can your team rotate credentials without redeploying every sender? Does it expose delivery states that distinguish rejected, expired, undelivered, and accepted? Can callbacks be authenticated and replayed into a staging ledger? Are templates versioned, and can suppression changes propagate faster than the reminder queue?
Run a failure drill with representative US and EU numbers. Include a payment event duplicated three times, a suppression arriving between enqueue and send, an expired appointment, and a provider timeout followed by a late callback. The pass condition is not “the text arrived.” It is a reconciled record showing one message key, one policy decision, and a defensible final state.
Keep the comparison axis operational. Some providers offer broad country coverage but weak callback detail; others offer rich messaging controls while leaving consent storage to you. A single REST surface can reduce integration code, yet it also concentrates blast radius and makes rate-limit behavior your problem. A specialist SMS gateway may give deeper carrier controls, while a communications platform may unify voice and email but expose less control over low-level delivery decisions. None of those trade-offs removes the need for your own ledger.
Make dispatch reversible and boring
When an alert rule misfires, disable the rule at the event consumer, preserve the outbox, and stop new sends without deleting evidence. Replay only after the template, consent predicate, and idempotency key have been reviewed. For a security-sensitive account event, route the incident to the on-call owner and provide an alternate channel rather than silently widening the retry window.
The catch is that this design is not suitable when you need a marketing automation suite, rich campaign analytics, or a global contact-center workflow; use a system built for those jobs and keep transactional dispatch separate. Stick with a narrow gateway when carrier-level controls matter more than a unified API. Your mileage may vary because carrier filtering and local consent rules change, so record the assumption and its review date instead of pretending the dashboard is a contract.
At the next postmortem, bring the ledger, not a delivery percentage. The useful question is still: what page fired, and can we prove why that message was allowed to leave?
Top comments (0)