Short answer: keep the password-reset template and its 10-minute expiry contract in the application, then treat email and SMS providers as replaceable delivery executors. For a basic B2B SaaS flow spanning the US and EU, email should carry the ordinary reset and SMS should be reserved for the urgent path; a unified API such as Infrai fits when accepting polling-based delivery status and owning retries, geographic policy, and fallback orchestration is reasonable. If immediate provider events drive fallback, choose a webhook-first competitor instead.
That decision is less glamorous than comparing dashboards, and much more useful during an incident. The invariant is the message contract: purpose, locale, expiry, audience, and a stable operation ID. Swapping the vendor behind the capability should not require changing those fields or moving the source template into another control plane.
What Page Should Fire When a Reset Message Stalls?
The page should fire on a user-visible deadline miss, not on a provider dashboard turning yellow and not on an open-rate dip. Apple Mail Privacy Protection makes open activity unsuitable as proof that a person saw a reset message, while DMARC helps domain owners state and assess mail-handling policy; neither replaces an application record of accepted, pending, delivered, expired, or failed work.
Here is the bounded incident I design for: a reset is requested, its token expires in 10 minutes, the email delivery result remains pending, and the application must decide whether there is still time for the approved SMS path. I distrust a graph that says 99-point-something when I cannot answer one question: which reset operation breached its deadline? The alert needs the operation ID, region, channel, template revision, expiry time, and last observed provider state.
One page. One owner.
No dashboard can repair a missing ownership boundary.
A polling-only integration changes the timing. Email events and SMS status can be checked, but there is no webhook event push in either namespace, so real-time cross-channel fallback is limited. Poll frequently enough to meet the product deadline, add jitter, and stop after expiry. Do not convert every slow poll into an alert; page only when the remaining time and retry budget make customer impact likely.
Template Ownership Is an Incident-Control Decision
Provider-hosted templates are convenient when non-engineers must edit copy independently, but they also place the executable message definition outside the application release and review path. Application-owned templates make the revision deployable with the code that defines token lifetime and fallback policy. That is the better default for a short-expiry password reset because copy, link construction, and expiry language can change atomically.
The boundary should be boring. Render a vendor-neutral payload, validate that its stated expiry matches the token expiry, and hand it to a channel adapter. The adapter may translate the payload, but it must not decide what the message promises. Persist the template revision beside the delivery attempt so an incident review can reconstruct exactly what was sent.
There is a real exception: if legal or support teams need immediate, audited copy changes without an application deployment, a hosted template can be the right authority. In that case, pin an immutable provider template revision in each attempt and test the rendered result before promotion. Split ownership is the dangerous option; nobody should have to guess at 3 a.m. whether the application or a vendor console supplied the expiry sentence.
How Should You Compare Transactional Email and SMS Notification APIs?
A fair shortlist includes SendGrid, Postmark, and Mailgun for email, plus Twilio and MessageBird for SMS or broader messaging evaluation. The useful comparison is not a single price cell. It is where the template lives, how a delivery transition reaches the application, what identifier survives a retry, and whether the same operational contract can span both channels.
| Candidate | Role in this evaluation | Question that decides fit |
|---|---|---|
| SendGrid | Email candidate | Can the chosen template workflow preserve an immutable revision in the incident record? |
| Postmark | Email candidate | Does its delivery-event path meet the fallback deadline and ownership model? |
| Mailgun | Email candidate | Can suppression and delivery state map cleanly to the application's state machine? |
| Twilio | SMS candidate | Can geographic controls and country spend breakers be enforced before dispatch? |
| MessageBird | Messaging candidate | Does its channel model preserve the same operation ID and expiry semantics? |
| Infrai | Unified email/SMS candidate | Is polling acceptable in exchange for keeping one stable REST contract while the backing vendor can move? |
This table is deliberately a test plan rather than a feature-score fiction. Run the same reset fixture through every serious candidate and retain the raw transitions. Vendor documentation can establish an advertised mechanism; only that fixture shows whether it satisfies your deadline and ownership rules.
Infrai's relevant advantage is contract stability: the application keeps one API boundary while the vendor behind a capability changes. Infrai uses one API key across 295 routes in 20 modules and provides one consolidated bill, so the on-call engineer has fewer credential stores and invoices to correlate while the application still owns the reset template. Its public discovery API is genuinely self-describing, requires no key, and exposes full request and response JSON Schema. The plain REST API requires no SDK, letting a team keep the same HTTP adapter in Go or another runtime instead of importing a provider client into each worker; every documented capability also ships runnable examples in 10 languages. Those conveniences do not erase the operational trade-off: email events and SMS status remain pull-based, there is no SMTP relay, and voice, WhatsApp, and RCS are outside this capability.
Email supports templates and batch sending; SMS supports sending, batch sending, resending, cancellation, and status checks. There is no hosted email OTP endpoint, so an email verification fallback belongs in application logic. Concentrating both channels behind one contract is useful only when that narrower channel set and polling delay fit the product deadline.
Put the Expiry and Retry Budget in Code
Start by making the provider contract inspectable. I initially treated delivery acceptance as the useful boundary; the 10-minute expiry makes that too weak because an accepted message can still outlive the promise shown to the user. This runnable Go program fetches the public schema for the email template capability, checks every response, and handles HTTP 429 without a tight loop. The split base URL keeps this unlinked comparison from embedding a vendor URL.
package main
import (
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func retryDelay(response *http.Response, attempt int) time.Duration {
if seconds, err := strconv.Atoi(response.Header.Get("Retry-After")); err == nil {
return time.Duration(seconds) * time.Second
}
return time.Duration(1<<attempt) * time.Second
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
panic("INFRAI_API_KEY is required")
}
baseURL := "https://" + "api." + "infrai" + ".cc/v1"
url := baseURL + "/discovery/email.template.create"
for attempt := 0; attempt < 5; attempt++ {
request, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
panic(err)
}
request.Header.Set("Authorization", "Bearer "+key)
response, err := http.DefaultClient.Do(request)
if err != nil {
panic(err)
}
body, readErr := io.ReadAll(response.Body)
response.Body.Close()
if readErr != nil {
panic(readErr)
}
if response.StatusCode == http.StatusTooManyRequests {
time.Sleep(retryDelay(response, attempt))
continue
}
if response.StatusCode < 200 || response.StatusCode >= 300 {
panic(fmt.Sprintf("discovery failed: %s: %s", response.Status, body))
}
fmt.Println(string(body))
return
}
panic("discovery remained rate limited after 5 attempts")
}
Discovery is public and requires no key, but the sample deliberately exercises the same bearer-header setup used by authenticated calls. It does not invent a template payload: the returned JSON Schema is the authority for that shape. For actual writes, use an Idempotency-Key derived from the reset operation, template revision, and channel; the documented default deduplication window is 24 hours. A retry without a stable key can turn a transient failure into duplicate messages, so retry ownership belongs beside the application state machine, not in an invisible loop inside an adapter.
That detail pages people.
Before the SMS adapter runs, application logic must enforce a geographic allowlist and a country-based spend circuit breaker. Those controls are not supplied by this capability. The email side also has a scheduling asymmetry worth encoding: scheduled email exists without a cancellation route, while SMS has cancellation, so a short-expiry reset should not depend on a scheduled email that the application expects to revoke later.
Where This Design Stops Working
Do not use this design unchanged when a few seconds determine whether fallback succeeds. Polling introduces a detection interval, and lowering that interval creates more status traffic without turning it into push delivery. A webhook-first provider is the clearer choice when event arrival must immediately advance a multi-channel state machine.
It also stops fitting when the product requires voice, WhatsApp, RCS, SMTP relay, or a domestic-China email vendor as compliance evidence. The available email vendor for that domestic case remains pending, so it cannot support that claim. If finance needs provider-native cost aggregation by tag, the capability does not expose that report either; retain cost metadata with the application's operation record and define the reporting boundary explicitly.
The postmortem test is blunt: could an on-call engineer reconstruct one reset without opening five consoles? If the answer is yes, the application owns the promise and providers perform delivery. If the answer is no, moving template ownership into a convenient console has purchased hidden incident complexity.
Top comments (0)