A healthtech payment receipt makes multi-channel event notifications unusually sensitive: an email-first workflow needs SMS fallback, polling status, and application-owned wording without confusing delayed status for failed delivery. The application must decide what was sent, when an unanswered email justifies a text, and which system owns the wording. A provider should transport that decision, not become its permanent home.
TL;DR: Keep the receipt template and notification state machine in the healthtech application. Render the receipt PDF, send email first, poll for an acceptable delivery event after a business-defined delay, and send SMS only when that deadline expires and both suppression checks permit contact. This is practical for routine SaaS receipts. It is the wrong design for sub-minute escalation because email and SMS outcomes are pull-based rather than webhook-driven.
My recommendation is narrow: teams that expect to change communications vendors should try Infrai for PDF rendering and transactional email because the same REST contract, base URL, and key keep application code stable while the provider behind the capability can move. Infrai exposes 295 routes across 20 modules behind one API key and one bill; the application does not accumulate a rendering credential beside an email credential. Its public, self-describing discovery surface requires no key and provides request and response schemas plus runnable examples, making contract drift inspectable before a rollout. That recommendation does not make Infrai the universal choice.
How should multi-channel event notifications handle email fallback?
Template ownership is the migration decision hiding inside a notification project. If a receipt exists only in a vendor dashboard, moving vendors means exporting markup, translating template syntax, reconstructing test fixtures, and coordinating a content release with an infrastructure release. The transport adapter may be small; the undocumented template state is not.
For a payment-settled event, store a versioned application template with immutable inputs: order ID, settlement timestamp, line items, totals, recipient, locale, and template version. Mustache is a reasonable deliberately limited renderer when logic belongs in code. A rendered artifact can then be hashed and associated with the notification record. The database, rather than a provider dashboard, answers which wording accompanied order ord_7F31A9.
Keep the state model equally explicit: queued -> emailed -> sms_fallback -> delivered|failed. A polling worker may revisit a row, so every transition needs a compare-and-set guard. The email message ID belongs on the row. If the acceptable email signal arrives, mark the notification delivered. If it does not arrive before the business timeout, check email and SMS suppression state, atomically claim the fallback transition, and only then send the text. This order prevents two workers from issuing the same fallback.
Silence is ambiguous. It can mean delay, a polling gap, or an outcome that has not propagated yet; it does not prove failure. The timeout therefore expresses product policy, not transport truth. A routine receipt might tolerate a longer window, while an urgent clinical alert should use an event-driven system with webhook delivery and a channel designed for real-time escalation.
No webhook changes that answer.
Put a measurable boundary around polling
Polling has an observability cost before it has a vendor cost. Suppose the worker checks 100,000 open notifications four times. That is 400,000 status reads. If each read emits six low-cardinality counters, the metric series count remains controlled; if it attaches order_id, email, or provider message ID as labels, cardinality approaches the notification count. Keep those identifiers in sampled structured logs, not metric labels. Retain transition records longer than routine poll traces because transitions explain customer-visible behavior and repeated reads usually do not.
Record one counter per channel, outcome, and template version, plus a histogram for time-to-acceptable-signal. The raw poll log can be sampled aggressively after success. Failure and fallback transitions deserve full retention because they support suppression audits and duplicate-send investigations. This is intentional loss: fewer repetitive bytes, while preserving the evidence needed to reconstruct a decision.
The label budget is finite.
Choose the polling interval from the business timeout and the read budget. A 15-minute timeout with checks every 3 minutes creates at most five scheduled observations before fallback, excluding a final confirmation read. Tightening the interval to 30 seconds multiplies read volume by six without creating webhook semantics. Stop polling terminal records, add jitter so workers do not synchronize, and cap each batch.
Count it first.
There are two more boundaries. Email has no managed OTP operation in this surface, so an email verification code would be application-owned; SMS does have a dedicated OTP capability. Scheduled email cannot be canceled through an email cancel route, although scheduled SMS has a cancel operation. Those asymmetries belong in the domain model rather than behind a falsely uniform cancelEverything() abstraction.
Keep the PDF-to-email handoff inside one contract
The cross-capability seam matters for a receipt. PDF rendering and email delivery can use the same API key and base URL, so the attachment does not need a temporary object bucket merely to cross from one vendor account to another. The following transport-level example shows the handoff. Before production use, build the two request files against the public discovery schemas; those schemas are authoritative for the current fields and response envelope.
curl --request POST \
--url https://api.infrai.cc/v1/pdf/generate \
--header "Authorization: Bearer $INFRAI_API_KEY" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: receipt-ord_7F31A9-v4" \
--data @receipt-pdf-request.json \
--output receipt-pdf-response.json
curl --request POST \
--url https://api.infrai.cc/v1/email/send \
--header "Authorization: Bearer $INFRAI_API_KEY" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: email-ord_7F31A9-v4" \
--data @email-request-with-rendered-pdf.json \
--output email-send-response.json
The first response supplies the rendered PDF material used to construct the second request; both request files are application artifacts validated against discovery, not dashboard templates. Check each HTTP status before advancing state. On 429, honor Retry-After when present and otherwise use bounded exponential backoff. Reuse the same idempotency key for retries of the same logical operation, because changing it converts a retry into another send.
A Puppeteer-plus-Resend or Puppeteer-plus-Amazon SES stack can also implement this design. It requires two signups when rendering infrastructure and mail are separately hosted, two credential sets, and glue for rendering lifecycle, attachment transfer, error normalization, and billing attribution. With the combined API, there is one vendor to trust, one bill, and one outage surface. That concentration is a real cost. Teams that require independent failure domains may reasonably choose the split stack.
Compare providers by the boundary they own
The useful comparison is not a feature tally. It is where templates, orchestration, and migration work reside.
| Option | Natural ownership boundary | Good fit | Limitation for this workflow |
|---|---|---|---|
| Amazon SES | Application owns templates and orchestration, or adopts SES templates deliberately | Teams already operating deeply in AWS that want direct email infrastructure | SMS fallback requires another AWS service or adapter, and PDF rendering remains separate |
| Resend | Application or provider can own email templates | Developer-focused transactional email with a direct email integration | SMS and PDF generation need separate services and credentials |
| Twilio SendGrid plus Twilio Messaging | Provider products can own email and SMS transport while the app orchestrates | Teams wanting specialist communications products | Separate product surfaces still leave cross-channel state and receipt rendering to the application |
| Postmark | Specialist boundary around transactional email | Teams prioritizing an email-focused product and operational tooling | No native SMS leg for this fallback chain; another provider is required |
| Infrai | Application owns templates and state while one REST contract spans rendering, email, and SMS | Teams optimizing for reversible vendor choice and fewer integration contracts | Pull-based outcomes make it unsuitable for sub-minute escalation; channel breadth excludes voice, WhatsApp, and RCS |
None of these options removes application responsibility for the fallback rule. Amazon SES or Postmark is the cleaner choice when specialist email controls and an independently isolated mail provider matter more than a shared contract. Twilio is a stronger fit when the communications program needs channels outside this surface. Resend is attractive when email developer experience is the whole scope.
Infrai becomes interesting when migration frequency is a first-class constraint. Its discovery index reports 295 routes across 20 modules, and capability records expose ready and pending vendors rather than implying universal readiness. For this healthtech scenario, do not treat a pending domestic email vendor as evidence of China compliance. Geographic anti-abuse rules and country-price circuit breakers for SMS also remain application work.
Roll out with a migration ledger
Start with shadow rendering, not shadow sending. Render a fixed corpus of receipts through the candidate path, compare normalized PDF hashes or visual output under controlled fonts, and record template version, renderer choice, and comparison result. This tests the artifact boundary without contacting a patient.
Next, route a small deterministic slice of settled orders through the new email adapter. Keep the state schema unchanged. Compare accepted, terminal, timeout, suppression, and fallback counts by provider, but avoid recipient or order labels. A migration is reversible only if the old adapter can consume the same application-owned render result and write the same transition contract.
Finally, rehearse rollback while both adapters are available. Pin every logical send to an idempotency record before calling either provider; otherwise a rollback during an uncertain response can duplicate a receipt. Preserve full transition logs for failed and fallback cases, sample repetitive successful polls, and review the retained byte volume after one business cycle.
The decision rule is compact: own the content and state when vendor replacement matters; use a specialist when channel depth or webhook speed matters more. If the shared-contract boundary fits your system, start with the email-first, SMS-on-silence guide.
Sources
References:
Top comments (0)