Voice broadcast with SMS fallback, threaded back into the Inbox
This walkthrough composes the whole voice → SMS fallback campaign end to end: route the waterfall, build the payload, clear the compliance gates, launch, read the per-hop receipt, and receive SMS replies in the Inbox. It stitches the broadcast side (voice broadcasts), the fallback pattern (channel fallback recipe), and the reply loop into one run. Use this page when you want a voice call to lead and an SMS to catch recipients the call could not reach — appointment reminders, outage notifications, delivery windows — with replies landing where your team answers them. For the dashboard one-shot workflow with no fallback, stay on the voice broadcast guide. For OTP-style voice fallback, use a Verify profile instead of hand-rolling the chain.1. Why cascade_policy drops voice — route as a /notify waterfall instead
The smart-send resolver filters voice (and fax) out of every stamped fallback chain — the terminal-DLR escalation hook applies the same eligibility, so cascade_policy: { fallback_channels: ["sms"] } can never carry a voice hop on either side of the arrow. The channel fallback recipe calls this out for voice → SMS specifically.
The surface that does start on voice is POST /notify in mode: "waterfall": you pass the recipient across two or more bindings[], the binding order is the chain order, and hop 0 fires immediately. The remaining hops arm as the escalation tail and advance only when the active hop reports a terminal failure inside fallback_window_seconds.
Two consequences you plan around:
- Voice-first means voice is synchronous. The voice hop queues in the POST; if it rejects synchronously (unprovisioned sender, a per-channel opt-out, a bad from-number), the waterfall advances in-band to SMS before any DLR exists. The POST response names which binding queued in
activeand which it skipped inskipped[]. - The fallback window bounds staleness. An undelivered receipt that lands after
fallback_window_secondsdoes not escalate — the send is too stale to be useful. Set it per recipient cohort, not once globally: a “your appointment is in 2 hours” reminder warrants 300–900 seconds, an outage notice warrants longer (floor 30s, ceiling 24h; default 24h when omitted).
2. Compose the broadcast payload — two bindings, one envelope
A two-binding waterfall for one recipient looks like this. Voice is binding 0 (fires first), SMS is binding 1 (armed; fires only on a terminal voice failure inside the window):bindings[]— the ordered DeliveryChain. Hop 0 (voice) carries the spoken script; hop 1 (SMS) carries a shortened copy restated for text — mirror the purpose and include opt-out phrasing on the SMS hop (Reply STOP …), since this may be the recipient’s only touch with the message.fallback_window_seconds: 600— per-send staleness window. A voiceno-answer/busy/failedreceipt inside ten minutes fires the SMS; one that returns eleven minutes later does not.metadata.campaign— a shared billing topic stamped onto every hop’s message row, so cost and delivery reporting (GET /notify/:notifyId, campaign exports) attribute both hops to the same campaign rather than splitting them across orphaned message ids.max_price(per binding) — per-step cost ceiling in USD. When the resolved cost of that hop would exceed the cap, the hop is rejected before the wallet is billed, so an expensive hop never fires even once.max_total_price: 0.27— cumulative cascade cap across every hop of this waterfall. If the running total would exceed it, the remaining tail is trimmed and reported asdropped_for_cost_cap. Fan-out mode ignores it — there is no cascade to cap.
from + shared body on the envelope apply to any binding that omits its own; per-binding body, from, template_name, and max_price win on conflict. In practice the voice and SMS scripts always differ in length and phrasing, so per-binding bodies are the norm in a voice → SMS waterfall.
Voice clone and the dialing window. To speak the script in a cloned voice, create the clone first (signed consent + a short sample script — the voice clones guide covers the consent and quality gates), then reference the clone through the voice sender profile the from number resolves against. A voice clone changes the spoken rendering, not the compliance footprint: the federal 8 AM–9 PM recipient-local dialing window and your workspace quiet hours still apply to the voice hop, and neither a clone nor a stock voice can relax them.
Guard: a one-binding waterfall is a 422.
mode (fan-out of one) — or use the campaigns surface.
Fan-out vs waterfall for this pattern. mode: "fanout" (the default) fires every binding at the same time — voice and SMS together. For a fallback you want waterfall, where the SMS only spends money after voice terminally fails; fan-out is for “alert on every channel now” traffic and burns both hops every time.
3. Enforce TCPA and DNC before the voice leg
The voice hops in a waterfall are ordinary outbound calls — every gate the broadcast composer applies applies here, on every recipient in every binding. Clear these before you queue a batch of waterfalls: TCPA federal window (non-relaxable). Every voice hop is stamped against the federal 8 AM–9 PM recipient-local dialing window at dispatch. A recipient whose local time falls outside the window at send refuses with422 TCPA_FEDERAL_DIALING_WINDOW_BLOCKED. This is a schedule fix, not a config fix — no workspace setting relaxes it. For a broadcast-sized batch, schedule the launch so the whole recipient cohort stays inside its own local windows, or let blocked recipients land on the SMS hop (the synchronous rejection advances the chain in-band, and the SMS hop refuses only if SMS quiet hours also block it — see the TCPA posture guide for how SMS/WhatsApp quiet hours differ from the voice federal guard).
Workspace quiet hours. On top of the federal window, your quiet-hours configuration bounds the per-recipient dialing window and is checked at send. Verification of what your quiet hours actually resolve to lives under the compliance settings — run it before the launch, not after the first 422.
Tenant DNC pre-flight scrub. Suppressed and opted-out recipients are excluded at send time, but batching is cheaper than per-send rejection: run the tenant do-not-call source against your recipient list before launching. A scrubbed-off recipient on the voice hop shows up in the POST response as a synchronous skip with an opt-out code, and on SMS as RECIPIENT_OPTED_OUT when it reaches that hop — either way, the cleaner move is keeping them out of the bindings. The DNC pre-flight scrub guide walks the same scrub at single-recipient and batch scale; check the do-not-call source under Outbound → Audiences before the first batch of the day.
Recording-consent acknowledgement. If your voice hop renders into a recorded call (your number’s recording-consent posture), the campaign-style gate is acknowledgement; verify it at the workspace level before the first send.
4. Launch, then read the per-hop receipt
Launch with the POST above. The response confirms which binding is live and what is armed:…/notify/:notifyId GET aggregates the hop list as DLRs land — one status, one summed price, and per-hop error codes:
- Hop 0 voice, hop 1 SMS — the full cascade in one envelope. Only the hops that actually attempted bill:
cost.totalis the true cost of getting this recipient, not the sum of both hops’ ceilings.priceanderror_codeare per-hop, so aVOICE_NO_ANSWERon hop 0 does not poison the read on hop 1. status: "delivered"names the cascade outcome,delivered_channel: "sms"names who landed it. A cascade reportsfailedonly when EVERY hop terminated non-delivered and no escalation tail is armed;in_progresswhile any hop is still live or armed.skipped[]at POST time vserror_codehere. A binding that rejected synchronously (bad sender, opt-out) never entered the cascade; it is in the POST response’sskipped[], witherror.code. A hop that queued and terminally failed at the carrier shows here witherror_codeon its hop row.truncated: true(only past the hop ceiling) anddropped_for_cost_cap(only when you setmax_total_price) — both listed in the cascade receipt when they apply.
5. Run SMS replies into the Inbox — the reply loop closes the campaign
When the SMS hop is the one that lands, that number can reply — and replies need somewhere to go. The sender number on your SMS binding is the Inbox thread identity: replies to it open (or continue) a conversation with the same recipient pair, so your team answers in Inbox → Conversations instead of a webhook you build yourself. The inbox setup guide wires the number into a conversation surface; inbound SMS routing covers which number answers which kind of traffic. What propagates back, and where it lands:- A YES / R reply is a conversation row. The recipient’s YES confirms the appointment; that row belongs in the same Inbox thread the SMS hop sent from, with the campaign metadata intact, so an agent sees the full context (“Acme Dental reminder — replied YES”) without joining message ids. Keyword auto-reply rules can automate the acknowledgement when the reply is a known intent.
- A STOP reply is an opt-out, not just a message. Same thread, but the opt-out also writes to the tenant suppression list immediately — the next binding you build against this recipient is excluded at send time (voice hop skips it synchronously with an opt-out code, SMS hop refuses
RECIPIENT_OPTED_OUT). The opt-out rules guide is the semantics reference; the consent and suppression model concept covers how per-channel opt-outs land. - Voice-leg outcomes feed back too. A recipient who pressed a digit on the voice call (confirm / reschedule) never needed the SMS hop — the chain ended delivered at hop 0. That touch does not create an Inbox thread, but it shares the same
campaigntopic, so reporting unifies both outcomes. A recipient who neither answered the call nor received the SMS inside the window is the genuinely-unreachable cohort — the waterfall is what tells you exactly which recipients those are.
6. Sandbox validation before production
Prove the advance before real carriers are involved. A sandbox API key (dv_test_sk_*) turns the recipient’s trailing digit into a deterministic receipt — the sandbox magic numbers table lists every scenario. For this walkthrough, the two scenarios that prove the whole flow are: …3 (undelivered primary → escalation fires) on the voice hop and …2 (delivered) on the SMS hop. Re-run section 2 with a one-binding waterfall to confirm the 422 guard. The sandbox simulates receipts; it never rewrites your chain — what advances in sandbox advances in production.
7. Troubleshooting
- Voice hop never fires; SMS is immediate. Check the POST response
skipped[]— a synchronous rejection (opt-out, unprovisioned sender, bad from-number) advances the chain in-band before any DLR exists. - 422
TCPA_FEDERAL_DIALING_WINDOW_BLOCKEDon some recipients. Their local time was outside the federal window at dispatch. Schedule per recipient-local time; the window cannot be disabled. The SMS hop is unaffected and still catches them. RECIPIENT_OPTED_OUTon the SMS hop. The recipient opted out of SMS between the voice failure and the escalation firing, or you did not scrub DNC pre-flight. Verify the tenant DNC source and re-run the pre-flight scrub on the next batch.dropped_for_cost_capnames a channel you wanted. Yourmax_total_pricewas lower than the cascade’s resolved cost; raise the cumulative cap or drop the expensive hop.- SMS replies not threading into Inbox. Verify the sender number is an inbox-routed number (inbound SMS routing); a number with no inbox route keeps replies on webhooks only.
- The voice hop reports
deliveredbut you expected SMS. An answered call terminates the chain — no SMS fires. That is the waterfall working as designed; the recipient got the message by voice.
See also
- Channel fallback recipe — the generic two-step and three-step waterfall shapes, sandbox-tested.
- Fallback chains — every fallback surface compared (campaign ladders, smart-send policy, Verify).
- Voice broadcasts — the dashboard one-shot broadcast this walkthrough extends.
- Track a cascade — field-by-field reading of the notify receipt.
- TCPA posture and DNC pre-flight scrub — the pre-launch gates.
- Inbox setup and Opt-out rules — the reply loop and suppression side.