Smart-send timing playbook
“Smart send” names three different surfaces in Orbit, and they answer three different questions. This guide walks the campaign-side ones — when each contact should receive your campaign, and which channel each contact is likeliest to engage on — and keeps them straight from the per-message router and the fixed-window experiment.
The first is a per-contact prediction read from each contact’s engagement history. The third and fourth are a controlled experiment between fixed windows you choose. They are complementary, not competing: preview-smart-send tells you what the model predicts per contact; the experiment tells you, causally, whether a window you chose beats doing nothing.
For the dialer equivalent of per-contact timing (stamping each contact’s first dial attempt into their predicted answer window), see Best-time-to-dial.
Prerequisites
- An API key with the
campaigns:readscope (the preview and results endpoints are read-only; arming the experiment uses the standard campaign update call). - A campaign in draft or scheduled state. The previews resolve the campaign’s audience exactly as a launch would.
- Engagement history makes the predictions sharper. A brand-new workspace with no history still gets a schedule: contacts fall back to a timezone-aware local window derived from their phone number.
The smart-send family, disambiguated
Per-contact campaign schedule: preview-smart-send
POST /api/v1/campaigns/{id}/preview-smart-send walks the campaign’s resolved audience — list, segment, CSV upload, manual picker, or all contacts — and returns, per contact, the instant the engagement model predicts that contact is most likely to engage. The predictions come from each contact’s own engagement profile (the same per-contact history every smart-send surface reads); nothing new is collected.
The response is a bounded preview: by default the first 500 contacts, up to 5,000 with ?limit=. The full per-contact schedule for a 100k-contact audience is computed on the fly by the execution worker when the campaign runs — the preview exists so you can review the shape of the plan before launch, not to export it.
Per-message router: POST /messages/smart-send
POST /api/v1/messages/smart-send takes one ad-hoc message, picks a channel with the smart router, and dispatches. With a cascade_policy it also arms a fallback chain on that send. It never reads a campaign audience and produces no schedule. If your flow is “this one OTP must reach this one person on the best channel now,” that is the per-message router — covered in Per-message cascade policy on smart-send.
Fixed-window experiment: send-time-experiment/preview and /results
The send-time experiment does not predict a per-contact optimum. It splits the audience into randomized, mutually-exclusive cohorts — one per fixed UTC window you declare, plus an optional do-nothing control — sends each cohort at its window, and scores which window lifted conversion over control. Use it when you want a causal verdict between candidate windows (“9am vs 1pm vs 6pm”) rather than a per-contact prediction.
Two lift endpoints, two questions
GET /api/v1/campaigns/{id}/holdout-lift— did sending at all lift conversion? Treatment vs a do-nothing control. Covered in A/B test a campaign.GET /api/v1/campaigns/{id}/smart-send-holdout-lift— did sending at each contact’s predicted best time lift conversion over their unoptimized schedule? Only meaningful on a recipient-optimal campaign that reserved a timing hold-out (smart_send_holdout_pct); a campaign that never opted in returnsconfigured: false, not an error.
next_best_channel (below) is advisory per-contact output, while a static fallback chain — the campaign channels[] ladder or a per-message cascade_policy — is what actually arms escalation on undelivered sends. See Fallback chains for the chain surfaces.
The preview-smart-send contract
total_audiencevspreview_count. The schedule covers the preview slice; the whole audience is scheduled at send time.total_audienceis the matched audience after suppression and opt-out netting, the same cohort a launch dry-run reports as deliverable.profile_countvsfallback_count. Contacts whose time came from their own engagement history vs contacts who got the timezone-aware fallback. A high fallback share on a mature workspace usually means the audience skews to contacts you have never messaged.distribution_utc. Sends per UTC hour across the preview. A healthy per-contact schedule spreads through each recipient’s daytime; a spike in one UTC hour on a single-timezone audience is what you want to see.schedule. The per-contact map itself, ISO-8601 instants.
Next-best-channel: advisory per-contact channel picks
When — and only when — the campaign declares more than one channel (the primarychannel plus at least one entry in the channels fallback ladder, e.g. RCS with an SMS fallback), the preview response also carries a next_best_channel block:
source: "profile" means the contact has history on at least one candidate; source: "fallback" means the contact has none and the recommendation is the neutral default.
Two things the block deliberately does not do:
- It is advisory. It informs how you order the campaign’s channel ladder before launch. It does not change which channel the execution worker dispatches on first — dispatch follows the chain you configured, and escalation follows terminal-DLR fallback.
- It is not the static chain. A static fallback chain (Per-message cascade policy on smart-send, or the campaign ladder in Fallback chains) is an ordering you set and the platform executes on failure.
next_best_channelis a per-contact readout you may use to choose that ordering. If you want the first-hop decision made per contact at send time, that is the per-message smart router, not this preview.
next_best_channel block — there is nothing to recommend between.
The send-time experiment variables
Opt in by settingsend_time_experiment in the campaign’s variables with the standard campaign update call:
- Two to four windows. Fewer than two usable windows means no experiment — the campaign sends as a single, un-experimented blast. Windows beyond four are dropped.
hour0–23 andminute0–59, integers. A malformed window is skipped, not coerced to midnight.- Distinct clock times. Two windows at the same instant are deduped; a “winner” between identical times would be meaningless.
holdout_pct0–50. The randomized do-nothing control. Values above 50 clamp to 50; omit it (or send 0) for no control — but then windows are scored on raw conversion rate alone, and you lose the causal baseline.- Windows are UTC hour-of-day. Convert from your audience’s timezone before you choose.
label is optional and defaults to HH:MM. Each contact’s arm (a window, or control) is assigned deterministically per contact, so pause/resume and audience re-resolution never move a contact between arms, and the assignment is uncorrelated with your A/B variant split, the campaign-wide control holdout, and any journey holdout.
Preview the cohort plan before launch
POST /api/v1/campaigns/{id}/send-time-experiment/preview resolves the eligible audience (suppressed, opted-out, and address-less contacts netted out, matching the launch dry-run) and returns each window’s cohort with its next fire instant, plus the control size. It is read-only: calling it reports the assigned-arm mapping without arming or mutating anything.
Candidate sends are anchored to the campaign’s configured start: a campaign scheduled for next week shows each window’s first occurrence on or after that start, never before it. A campaign that launches immediately rolls each window to its next occurrence from now.
Reading the experiment results
GET /api/v1/campaigns/{id}/send-time-experiment/results re-partitions the audience into the same arms (deterministic assignment reconstructs them exactly), joins each arm’s real conversions, and scores every window against the control:
- Per window: treatment and control conversion rates, the absolute lift in percentage points with its confidence interval, and an
is_significantflag. winning_window_index: the window with the highest statistically significant lift over control; when nothing is significant yet, the highest raw conversion rate; ties break to the earlier-declared window.winner_is_significanttells you which rule applied.- The control row itself: how many contacts were held out and how many converted.
Worked example: preview, opt in, launch, read results
smart_send_holdout_pct so GET /campaigns/{id}/smart-send-holdout-lift can score optimized vs unoptimized timing afterwards.
Troubleshooting
- 404 on the preview. The campaign id must exist in the workspace; the preview resolves the campaign before anything else.
- High
fallback_count. New workspace, or an audience of contacts you have never messaged. The timezone-aware fallback is the designed behavior, not a failure — history accumulates as campaigns send. - No
next_best_channelblock. The campaign declares fewer than two channels. Add a fallback ladder entry (e.g. RCS primary with an SMS chain entry) and the block appears on the next preview. - Experiment not taking effect. Check the variables: the key is
send_time_experimentundervariables; two or more windows must survive validation (integer hours 0–23, minutes 0–59, distinct clock times). A payload with one usable window parses to no experiment and the campaign sends once. - Window times look a day late. Windows are UTC and roll to their next occurrence; a window whose time has already passed today fires tomorrow, and never before the campaign’s configured start.
See also
- A/B test a campaign — variant splits, the campaign-wide control holdout, and the send-time experiment summary.
- Per-message cascade policy on smart-send — the ad-hoc message router and static fallback chains.
- Best-time-to-dial — the same per-contact timing idea applied to dialer campaigns.
- Holdout and uplift measurement — reading lift reports and confidence intervals.
- Quiet hours — the org-wide gate that still shapes every scheduled instant at dispatch.