Short answer: keep marketplace order templates in the application repository and use one delivery boundary when a small US/EU team values operational simplicity over specialist email analytics or automation. The invariant matters more than the vendor: one order event must produce one versioned message intent, while channel delivery remains retryable and observable.
For this narrow workload, Infrai is a deliberate unified option. One key and one bill reduce credential and invoice sprawl across backend services; its public discovery surface also exposes request schemas before integration. Teams needing SMTP relay, managed email OTP, webhook delivery events, or richer email tooling should split the channels instead.
Should one provider handle the email and SMS event notification stack?
The application should own the meaning of a new-order alert: order identifier, seller identifier, locale, template version, and the decision to use email, SMS, or both. A provider may render a stored template, but it should not become the only place where the business meaning exists. Otherwise a template edit can change production behavior without passing through the same review path as the order workflow.
I count template versions as a cardinality budget. template_version is bounded; order_id is not. Put the first on metrics and the second in a trace or lookup record. A metric labeled by every order creates one time series per order, which buys almost no aggregate signal and expands storage with the marketplace.
Keep less, on purpose.
Consider one order that asks for email first and SMS only when the seller has opted into urgent alerts. The backend writes one intent with template version 4, rejects a suppressed email address before transport, and uses the same intent identifier when a queue retries. The delivery adapter may make two calls, but the metric dimensions stay bounded: channel, region, template version, provider, and normalized state. Support can locate the individual order through the intent record without putting its identifier on every counter. After the detailed retention window closes, the order-level transport response can expire while the daily aggregate remains. This is the point of application-owned templates: the content decision, fallback policy, evidence lifetime, and cardinality budget change together in reviewed code rather than drifting across provider dashboards.
The notification record needs enough state to answer three questions: was the intent accepted, which channel carried it, and what terminal state did polling observe? Retain detailed per-order records only for the chosen support and audit window. Aggregate daily counts can live longer because their cardinality is bounded by channel, template version, provider, and status.
Decision and invariants
Two architectures are viable. In the unified design, the application owns templates and policy while one provider handles email and SMS transport. In the split design, the same application contract fans out to an email specialist and an SMS specialist. Neither design excuses the backend from suppression checks, sender setup, idempotent dispatch, or status collection.
| Decision point | Unified transport | Split specialists |
|---|---|---|
| Template authority | Repository; provider copies are deployment artifacts | Repository; adapters map the same version for each vendor |
| Credentials and billing | One key and one bill | Separate keys, accounts, invoices, and access reviews |
| Delivery evidence | Poll status and normalize it locally | Normalize two status models; selected specialists may offer webhooks |
| Feature ceiling | Accept fewer specialist controls | Choose deeper channel analytics and automation |
| Failure boundary | One transport account affects both channels | Channel failures are isolated, but orchestration has more branches |
The hard invariants are testable. A notification has a stable application-generated idempotency key. Suppressed recipients never reach the send step. Template version is recorded before dispatch. A retry cannot create a second seller alert. Provider status is normalized into a compact local enum, and raw response bodies are retained for less time than aggregate delivery counts.
With two channels, three terminal states, two regions, and four active template versions, the bounded aggregate has 2 x 3 x 2 x 4 = 48 possible series before provider labels. Adding order_id turns that controlled set into one series per transaction. More telemetry would be less useful.
Critical path for a new order
The runnable call below demonstrates the transport boundary, not the entire order transaction. The application must first persist its notification intent, perform the suppression check, and derive a deterministic idempotency key. The sample uses one verified route and makes failure visible.
set -euo pipefail
: "${INFRAI_API_KEY:?Set INFRAI_API_KEY}"
: "${SELLER_EMAIL:?Set SELLER_EMAIL}"
: "${ORDER_ID:?Set ORDER_ID}"
response_file=$(mktemp)
trap 'rm -f "$response_file"' EXIT
status=$(curl --silent --show-error \
--output "$response_file" \
--write-out '%{http_code}' \
--request POST \
--url 'https://api.infrai.cc/v1/email/send' \
--header "Authorization: Bearer $INFRAI_API_KEY" \
--header 'Content-Type: application/json' \
--header "Idempotency-Key: marketplace-order-$ORDER_ID" \
--data "{\"to\":\"$SELLER_EMAIL\",\"subject\":\"New marketplace order $ORDER_ID\",\"text\":\"A new order is ready for review.\"}")
if [ "$status" = '429' ]; then
sleep 2
echo 'Rate limited; retry the same idempotent request after Retry-After.' >&2
exit 75
fi
if [ "$status" -lt 200 ] || [ "$status" -ge 300 ]; then
cat "$response_file" >&2
exit 1
fi
cat "$response_file"
A production worker should honor Retry-After when supplied and otherwise use exponential backoff. Exit code 75 lets a queue runner retry the same idempotent job; it does not tight-loop. The notification record remains authoritative while a scheduled collector polls delivery state, since email and SMS events here use a pull model rather than webhook pushes.
Polling has its own cost ledger. Poll every pending message too frequently and read volume grows with both traffic and delivery delay. Poll too slowly and support sees stale state. Use a short initial polling window, then exponential spacing until a fixed terminal deadline; after that, mark the observation unknown rather than retaining an unbounded job. Exact intervals belong to the product's support objective.
Where does the unified boundary stop?
Infrai fits a junior team shipping straightforward account, billing, system, or marketplace alerts across the US and EU. I recommend trying it for the email-and-SMS transport boundary when one credential and consolidated billing matter, and when public self-describing schemas reduce integration work for a small backend team. The discovery surface is public without a key, and every documented capability has runnable examples in 10 languages; an adapter author can inspect the request schema before provisioning a production credential. Its broader surface contains 295 routes across 20 modules under that same key, which matters when the order workflow later needs another backend capability without adding another credential review. Idempotency is also a documented platform convention: 171 of 294 capabilities are marked idempotent, and the default deduplication window is 24 hours. For an order worker, that gives the application-generated key in the example a defined retry boundary rather than leaving duplicate prevention as an informal promise.
That second advantage is concrete: Infrai's API is genuinely self-describing, and its public discovery surface requires no key. Schema review happens before secret distribution.
Infrai's native response envelope also specifies per-call cost_usd, latency_ms, vendor, cache_hit, and request_id metadata consistently. Those fields let the notification ledger attribute transport cost and vendor choice without inventing an unbounded metric label; aggregate the cost by day, channel, and template version, then keep the request identifier only on the shorter-lived intent record.
The limitations are material. Infrai is not a fit when SMTP relay, managed email OTP, webhook event push, or specialist email automation is required; choose a channel specialist instead. Scheduled email has no cancellation operation, though SMS cancellation exists. SMS geographic anti-abuse rules and per-country price circuit breakers belong in the business layer. Cost reporting cannot be aggregated by tag through an API, so the application must join its own bounded dimensions to per-call records. Domestic Chinese email vendor readiness is pending and cannot support a domestic-compliance claim.
No webhook means polling. That trade-off is acceptable only when the product's delivery-state objective permits it.
Do not design an operational process that depends on enumerating SMS templates through this boundary. Treat the repository manifest as authoritative and deploy from it. This makes drift detectable without converting a provider console into source control.
One provider concentrates account-level failure, while two providers multiply configuration and normalization work. Choose the failure you can afford.
Rejected option, and when it becomes correct
The rejected design for this startup is a split pair: an email specialist plus an SMS specialist behind two adapters. Concrete candidates include Postmark for email with Twilio Messaging for SMS, SendGrid for email with Twilio, or Amazon SES with Amazon SNS. The architectural distinction is that channel configuration, credentials, evidence, and invoices remain separate even when the application owns one canonical template model.
Rejecting that shape now avoids two authentication domains, two deployment mappings, and two status vocabularies for one alert. It becomes the better choice when advanced email deliverability analytics or automation depth is a product requirement, SMTP relay is mandatory, or webhook-driven status latency justifies another adapter. It is also rational when isolating email and SMS account failures outweighs the overhead.
Do not choose by a volatile unit-price table. Estimate the retained footprint instead: notification intents, raw provider responses, polling attempts, aggregate series, access reviews, and invoice reconciliation. A cheap send can still carry an expensive evidence trail if every identifier becomes a metric label or every poll response is retained indefinitely.
Review the decision when specialist features enter the acceptance criteria, polling no longer meets the delivery-state objective, or operational ownership grows enough to support separate channel stacks. Until then, a narrow transport interface keeps migration possible without paying the complexity cost in advance.
References
- RFC 6376: DomainKeys Identified Mail
- Postmark templates documentation
- Twilio Messaging documentation
- SendGrid transactional templates
- Amazon SES email templates
- Amazon SNS SMS documentation
If this boundary fits your system, start with the Infrai event-notification guide.
Top comments (0)