Skip to main content

Your first multi-channel campaign: SMS with WhatsApp fallback

For the full campaign lifecycle — approval gates, drip and journey shapes, advanced segments, holdout and ROAS measurement — use Send a campaign end-to-end. This page is the shortest complete path to a single send with a fallback chain.
One scenario, one page: an abandoned-cart recovery nudge that reaches each recipient on SMS first, and falls back to a WhatsApp approved template (or RCS, if that’s live on your account) when SMS hits a terminal failure. One sending sender number, one segment, one preflight pass, one KPI to read back. No agents, no CDP event triggers. You will:
  1. Pick the segment
  2. Preview the message
  3. Create the campaign with a fallback chain
  4. Run the preflight gates
  5. Send
  6. Watch the delivery log
  7. Measure one KPI

Prerequisites

  • An API key with campaigns:write (reads below also accept campaigns:read). Create one in Settings → API Keys.
  • An SMS-capable sender you own. Buy one per Your first SMS, end to end step 2. For US traffic, complete 10DLC registration.
  • A connected WABA (WhatsApp Business Account) if you use the WhatsApp fallback — Meta bans inbound-only: connect one per WhatsApp onboarding. The WhatsApp hop needs an approved template for business-initiated sends.
  • An opted-in segment. Recipients must have given the right consent for the channel you reach them on. Start from a sandbox key (dv_test_sk_…) and swap a live key in for production.
Scope the sender to one number: every step below uses +18005551234. Outbound SMS/MMS always terminates through the Devotel wholesale network — the sender field chooses only which of your identities it presents.

1. Pick the segment

A campaign on this page targets one segment — a dynamic, filter-based audience — by audience_type: "segment" with a audience_id. Segments re-evaluate at send time, so an abandoned-cart segment scoped to “cart event in the last 24h” picks up fresh recipients instead of a frozen list. Count what the segment resolves to before you launch — POST /campaigns/audience/preview returns matching_count plus a 10-contact sample:
Pass channel and the response also returns a net projection: gross matches minus suppression rows, minus channel opt-outs, minus contacts with no deliverable address. If matching_count is 0, fix the segment before going further. The CDP segments guide covers segment definition; the audience pre-flight review guide covers the resolution math.

2. Preview the message

Templates use {{token}} placeholders resolved per-recipient from contact fields. SMS primary carries the campaign-level message_template; the WhatsApp fallback carries its own approved-template identity on the chain entry (the channels array in step 3 — the field binds to the exact chain step because a campaign-level template name is meaningless across heterogeneous channels). Resolve {{first_name}} and per-recipient tokens against sample contacts before launch — campaign personalization preview walks the response shape; a typo’d {{frist_name}} resolves to an empty string at send time instead of failing loudly, so catch it here.

3. Create with a fallback chain

The channels array is the fallback chain: an ordered list of up to 8 channels. Each non-final entry carries a fallback_on trigger (failed / no_delivery / no_engagement) and a fallback_after_seconds window (60–604,800 seconds). The last entry is terminal and must not carry fallback fields. The top-level channel must equal channels[0].channel. The full ordering rules live in fallback chains — this step wires the SMS → WhatsApp case:
fallback_on: "failed" advances on a terminal DLR (failed / undelivered / rejected) — the cheapest trigger, moved only when a hard failure is known. fallback_after_seconds is ignored on that trigger but the schema still accepts the value; use it consistently so an operator reading it later can set no_delivery without a shape change. no_delivery advances when no DLR arrives within the window; no_engagement advances on delivered-but-no-open/read — reach for it only after measuring engagement semantics. The WhatsApp hop bills independently per recipient — every chain is per-hop billed, verified in fallback chains §6. The response returns the campaign id (cmp_…) in data.id — hold it for preflight and launch.

What to skip on your first send

Some surfaces exist for second and third campaigns — defer them deliberately:
  • A/B variantsA/B testing splits an audience across two message bodies. Measure a single message first.
  • Quiet-hour per-campaign overrides — stick to the org-quiet-hours gate (set in quiet hours configuration); per-campaign overrides add a second policy to audit. The preflight dry-run projects the skip estimate under the org gate.
  • Holdout control cohorts — a holdout tells you whether the send beat doing nothing. Readership on one KPI first; the holdout uplift guide is there when you need it.
  • ROAS attribution — revenue attribution needs conversion-goal wiring and is covered in campaign ROAS attribution. A delivery-rate KPI is a faster first read.

4. Run the preflight gates

The checklist in Outbound compliance pre-flight names the gates a campaign send walks. For this single-send flow run the same pass — wallet, opt-out/suppression, quiet hours, frequency caps, compliance profile — and confirm each item below before launch. Copy the box state from that page; the send path will mechanical-422 anything missed:
  • Wallet is funded and not paused (GET /billing/balanceoutbound_paused: false).
  • Suppression list imported and liveOpt-out & suppression lists.
  • Quiet hours window chosen — org gate or campaign fallback window, decided deliberately.
  • Frequency caps set per channelFrequency caps.
  • Destination compliance profile attached where the market requires it (Compliance).
  • DNC check for a sample of recipients — run POST /compliance/dnc/scrub (up to 500 numbers per request) against the segment; clear rows pass.
Then run the read-only dry-run — it never mutates state, never touches the wallet, never enqueues a job:
The channel_waterfall bucket reports the primary channel and the fallback shape — verify it shows sms primary with whatsapp as the declared fallback. A healthy dry-run returns warnings: [] and cost.sufficient: true; anything else names the gate that would block.

5. Send

Launch with POST /campaigns/:id/send (or set scheduled_at for a future batch). A type: "blast" campaign sends the whole deliverable audience in one pass at the throttle_rate pace (platform default 50 msg/s when the field is 0 or absent). The throttle guide covers pacing; set throttle_rate explicitly once you know your volume.
The response 202s with the campaign back in running state. If your org requires supervisor approval the launch returns a pending-approval state instead — check GET /campaigns/approvals/pending from the approver’s queue.

6. Watch the delivery log

Subscribe to message.delivered / message.failed / message.received webhooks on one endpoint before the very first send — the receiver-loop shape is in Your first SMS, end to end step 4, and the per-campaign webhook events are in campaign lifecycle webhooks. For a page-into-the-log read, GET /campaigns/:id/recipients returns rows with per-recipient status and cursors (pagination.has_more / next_cursor):
Each fallback hop writes its own message id and its own status — a recipient who lands on the WhatsApp hop shows both the SMS failed row and the WhatsApp delivered row. Group block rows by gate (opted_out, frequency_capped, quiet_hours_blocked) the way the preflight checklist §8 maps them, and fix the audience or the gate — never resend blindly.

7. Measure one KPI

Pick one KPI per first send and read it back. For this flow the natural one is delivery rateGET /campaigns/:id/stats totals sent/delivered/failed/opened/clicked/replied:
The delivered rate divides delivered by sends that reached terminal state (delivered + failed), so a stuck non-terminal sent row doesn’t drag the number down. Compare that KPI across the primary and fallback hops on the same recipients, not campaign-wide — a healthy fallback chain shows fail-over adds delivered messages that would otherwise have been lost. When the deliverable number looks right and holds on the second campaign, layer the deferred surfaces (A/B, holdout, ROAS) in.

See also