Skip to main content

Per-contact channel optimization

A campaign sends every recipient on one declared channel. Per-contact channel optimization relaxes that: each recipient goes out on the channel their own engagement history says they respond to — picked from the channels you already declared on the campaign. A recipient who always opens RCS gets RCS; a recipient who answers SMS gets SMS; one campaign, the right channel per person. This page defines how the optimization decides, what bounds it, and how to turn it on. The engagement data itself (delivery, open, reply events per contact) is covered under the CDP event model.

Section 1 — The primitives that already existed

Two engagement-driven primitives shipped before this one. Both read the same per-contact engagement profile, and neither changed which channel a campaign dispatched on:
  1. Smart send (per-contact timing). With send_time_optimization: "recipient-optimal" on a campaign, each recipient is scheduled at their next best-time window instead of a single send-at. The ranking behind it also computes a per-recipient channel hint — exposed as the next_best_channel block on POST /campaigns/:id/preview-smart-send — but the hint was advisory only. The blast executor still dispatched everyone on the campaign’s declared channel.
  2. Delivery-based fallback. The cross-channel fallback chain walks a recipient down your channel list on a delivery failure signal (failed / no_delivery / no_engagement). That reacts to a failed send attempt; it never picks a better channel up front.
Channel optimization closes the gap: the same engagement ranking the preview surfaced now feeds real dispatch. Instead of “here is the channel this recipient would prefer — as a hint,” the executor dispatches them on it. One schedule is computed for the whole audience before the send loop starts, then consulted per recipient.

Section 2 — Where the ranking comes from

The ranking reads each contact’s existing engagement profile: the per-channel event counters the platform already maintains from delivery and engagement traffic (the same counters smart send reads for send-time prediction). There is no new schema, no new data collection, and nothing to enable upstream — campaigns that have been sending already carry the history this feature ranks on. Per candidate channel, the ranking compares how much that contact has engaged on that channel, with older events counting for less than recent ones. A contact with no usable history gets a fallback recommendation, and the campaign keeps them on the base channel. Because the profile is per-contact and per-channel, the ranking can’t invent a channel you didn’t offer it — it only ranks the candidate set the campaign supplies, which the next sections bound.

Section 3 — Opt-in contract

Channel optimization is opt-in, default off. A campaign turns it on with one key in its variables object — a JSONB field carried on the campaign resource, settable through the update-campaign API:
This is the same variables-keyed pattern as the other dispatch governors — adaptive_pacing, trust_tier_governor, and ab_bandit all live under campaign.variables — so one PATCH sets it and no migration or separate resource is involved. Absent, malformed, or "enabled": false means every campaign keeps its existing static-channel behaviour. The candidates the feature ranks are derived from the campaign itself:
  • The campaign’s base channel is always a candidate.
  • Every channel in the campaign’s fallback chain that sits inside the safe-swap envelope (next section) joins the candidate set.
  • If the base channel is outside the envelope, or fewer than two distinct safe candidates exist, there is nothing to optimize and the campaign sends exactly as before.

Section 4 — The safe-swap envelope

Per-contact channel overrides are restricted to the same channel set the budget-aware fallback already uses for mid-campaign downgrades: SMS, MMS, RCS, and Viber. Four properties make that set safe to switch per recipient:
  • All four are phone-addressed. The recipient is the contact’s phone number on each of them — no identifier remapping when the channel changes.
  • No per-loop setup is skipped. Email needs a subject and one-click unsubscribe headers; WhatsApp needs an approved template resolved before the send loop; voice needs consent, caller-ID, and TTS configuration. Those steps run once, keyed off the campaign’s declared base channel — a per-recipient switch into one of those channels would silently bypass them. The envelope excludes those channels outright, so an override can never route a recipient somewhere the campaign is not set up to send.
  • Push and social channels (messenger, instagram, line, telegram, kakao, zalo, wechat) are excluded for the same reason: identifier-based addressing, not E.164.
Consequence: a campaign whose base channel is email, WhatsApp, voice, or push never receives per-contact channel overrides — the feature only operates on campaigns that are already inside the envelope.

Section 5 — Credit caps stay an upper bound

The ranking is cost-blind: an engagement preference for MMS over SMS is legitimate, but MMS costs more per message. A campaign with a credit cap estimates its spend per batch as base-channel price × batch size — if an override routed recipients onto a pricier channel, real spend could exceed the estimate and breach the cap. So when the campaign has a credit cap configured, the candidate set is narrowed to channels whose per-message price is no more than the base channel’s, priced through the same source the cap estimate uses. The cap estimate then stays a strict upper bound on real spend. No cap configured — the full engagement-ranked candidate set is used.

Section 6 — Where the advisory preview ends

POST /api/v1/ai/optimize-channel still answers “which channel should a single message go out on?” for one-off sends — it ranks and recommends, and the returned ranking remains what your own sending logic does with it. That endpoint never touched campaign dispatch; channel optimization is what wires the ranking into the campaign executor. Use the endpoint for ad-hoc single sends, the opt-in for campaigns.

Safety and invariant notes

  • Tenant-owned controls only. The opt-in lives on the campaign resource you own; the ranking reads your tenant’s own engagement profiles, not any cross-tenant signal.
  • No new providers or channels. An override only re-labels which of your campaign’s already-declared channels a message goes out on. Outbound SMS, MMS, and RCS keep exiting through the Devotel softswitch exactly as before — the feature introduces no channel or provider the campaign didn’t already declare.
  • Fail-closed. Malformed opt-in config, an unpriced channel under a credit cap, or a failing profile read all resolve to “keep the base channel” — never to a guessed override.

See also