Scheduling behavior
Send times, timezone waves, fallback rules, and daylight saving transitions.
There are three ways a campaign can be sent, chosen by which fields are present. There is no mode parameter.
| Fields present | Mode |
|---|---|
| Neither | Immediate |
send_at | One absolute instant, the same for everybody |
local_time and timezone_fallback | A reading of each recipient's own clock |
send_at and local_time together are a schema error.
One instant, for everybody
{
"audience": { "all": true },
"send_at": "2026-12-24T18:00:00Z",
"payload": { "title": "Doors open", "body": "The sale starts now." }
}Replace the example date with a UTC instant in the future, at most 30 days from the request.
The answer is 202 with status: "scheduled" and an empty deliveries list: nothing is resolved
until fire time, so there are no identifiers yet. Follow the campaign through
GET /v1/messages/{id}.
The recipient's own hour
{
"audience": { "all": true },
"local_time": "10:00",
"timezone_fallback": "Europe/Paris",
"payload": { "title": "Good morning", "body": "Here is your day." }
}Devices are grouped into waves by the timezone they registered from, and each wave goes out when that reading arrives there: today where it is still ahead, tomorrow where it has already passed. Zones with the same send instant share a wave.
timezone_fallback is required, and a request without it is refused with
422 missing_timezone_fallback. A device that never reported a timezone has no local 10:00, so it
goes in the fallback wave, along with any zone that was not represented when the waves were
computed. No device is dropped from the campaign.
An identifier from the IANA database, always. Never an offset: Europe/Paris carries the daylight
saving transitions, +02:00 does not.
A device that registers late still joins
Waves resolve at fire time, not at creation. A handset that registers after the campaign was written joins it if its own wave has not gone yet.
Daylight saving
When a timezone transition skips or repeats the requested local time, these rules apply:
| Case | What happens |
|---|---|
| Nonexistent wall time, the spring-forward gap: 02:30 where the clock jumps 02:00 to 03:00 | Fires at the first valid instant after the gap, 03:00 |
| Ambiguous wall time, the autumn fall-back hour lived twice | Fires on its first occurrence, before the transition |
delivery_rate applies to the whole campaign rather than per wave. The waves are already spread
over a day.
Cancelling a scheduled campaign
POST /v1/messages/{id}/cancel works before a campaign has fired, and while it is still going out.
A scheduled campaign that has not resolved yet spares everything and answers
{ "canceled": true, "already_sent": 0, "canceled_count": 0 }. The count is zero rather than the
size of the audience because no delivery has been written yet.
Once sending has begun, what a provider already accepted stays sent. See delivery.