Send scheduled notifications
Schedule notifications to reach customers at specific future times—subscription renewal reminders, appointment confirmations, promotional campaigns, or payment due dates. Send to one recipient or broadcast to thousands with a single API call. Messages deliver at or shortly after the scheduled time without requiring you to maintain background workers or cron jobs.
Don't use Chimes for verification codes. If you need to authenticate or verify users with one-time passwords, use the OTP API instead. The OTP API handles token generation, expiry, validation, and rate limiting—critical security features that Chimes doesn't provide. See Verify customers with OTP to learn how to implement secure authentication flows.
How scheduling works
Scheduled Chimes become eligible for delivery at a future timestamp you specify. One schedule can reach one or many recipients. The API resolves saved customers and validates every inline phone number or email address before accepting the request. A malformed or unresolved recipient rejects the entire schedule; later transmission failures remain isolated to the affected recipient.
Recipients receive messages through SMS or email. Inline recipients use type plus a matching phone.number or email.address; saved-customer recipients use transport plus customer_id. All recipients in a single schedule must resolve to the same transport, so create separate schedules when a campaign needs both SMS and email delivery.
Email schedules use either a top-level email object or a stored email message_template object. SMS schedules use either full_message or a stored SMS message_template object. Do not include SMS and email content in the same schedule; the stored template channel must match the recipient transport. Email content is safety-scanned before the schedule is accepted.
{
"recipients": [{ "customer_id": "cu_gloria_k", "transport": "email" }],
"email": {
"subject": "Your subscription renews tomorrow",
"text": "Your subscription renews tomorrow. Update billing at https://yourstore.example/billing.",
"from": {
"address": "[email protected]"
}
},
"send_after": "2025-12-17T09:00:00Z"
}
Schedule a notification
Call POST /chimes/schedule with your message content, recipient list, and delivery timestamp. The API returns a schedule ID immediately—actual delivery happens at the specified time.
Schedule notification
curl https://api.zebo.dev/chimes/schedule \
-H "Authorization: Bearer $COMMERCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recipients": [
{ "type": "phone", "phone": { "number": "+233544998605" } },
{ "type": "phone", "phone": { "number": "+233501234567" } },
{ "type": "phone", "phone": { "number": "+233208765432" } }
],
"full_message": "Reminder: Your subscription renews tomorrow on Dec 18. Update payment method at https://pages.zebo.dev/invoices/billing if needed or contact support.",
"send_after": "2025-12-17T09:00:00Z",
"sender_id": "YourBrand",
"purpose": "subscription_reminder",
"request_meta": {
"idempotency_key": "sched_renewal_batch_dec_2025"
}
}'
The response confirms your schedule with an ID, recipient count, send time, and creation timestamp. Store the schedule ID if you need to track delivery later or reconcile sent messages with your records.
Recipient validation
Commerce resolves and validates every recipient during request processing. Inline phone and email contacts must be well formed, while saved-customer references must exist and contain a valid contact for the selected transport. If any recipient fails these checks, correct the recipient and submit the schedule again.
Acceptance does not guarantee later delivery. After execution, transmission failures are recorded per recipient and do not change whether the other accepted recipients can be sent.
Delivery timing
send_after controls when a schedule becomes eligible to execute; it is not a delivery-time guarantee. Allow for processing and network delays, and do not rely on sub-minute precision.
If you need immediate delivery, send a Chime instead. For time-critical notifications like password resets or purchase confirmations, send immediately rather than scheduling.
Broadcast to many recipients
Schedule one message to thousands of recipients with a single API call. Each recipient receives an individual Chime—no shared message threads or group texts. Track each delivery independently through the Chime API.
Broadcast campaign
curl https://api.zebo.dev/chimes/schedule \
-H "Authorization: Bearer $COMMERCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recipients": [
{ "type": "phone", "phone": { "number": "+233544998605" } },
{ "type": "phone", "phone": { "number": "+233501234567" } },
{ "type": "phone", "phone": { "number": "+233208765432" } },
{ "type": "phone", "phone": { "number": "+233277654321" } },
{ "type": "phone", "phone": { "number": "+233248888888" } }
],
"full_message": "Flash Sale! 30% off all items today only. Shop now: https://shop.example.com/sale Use code: FLASH30",
"send_after": "2025-12-17T08:00:00Z",
"sender_id": "ShopBrand",
"purpose": "marketing_campaign"
}'
The API currently enforces that recipients contains at least one item but does not publish a fixed maximum. Apply your own bounded batch size and monitor each schedule independently.
Managing scheduled notifications
Tracking execution and delivery
Check schedule status, view delivered message IDs, and identify failed recipients using POST /schedules/lookup. Returns the schedule details plus IDs of all created Chimes after execution. The errors array includes failure reasons for any recipients that didn't receive messages.
Track schedule status
curl https://api.zebo.dev/schedules/lookup \
-H "Authorization: Bearer $COMMERCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"schedule_id": "sch_kPvqTrqGsopu07wfC7ttoWqmfwt48ZW7BGvYUWk"
}'
Before execution, executed_at is absent and chime_ids may be omitted. After execution, chime_ids, when present, contains IDs of successfully created Chimes. Use Lookup a Chime with these IDs to check individual delivery status and transmission details. The optional errors array lists per-recipient execution failures.
Use this to debug broadcast campaigns: if 485 out of 500 messages delivered, the errors array tells you exactly which 15 recipients failed and why. Correct the invalid addresses and create a new schedule for just those recipients.
Canceling pending schedules
Cancel schedules before execution using POST /schedules/cancel. Prevents delivery if called before send_after time. Returns an error if the schedule already executed or was previously canceled.
Cancel schedule
curl https://api.zebo.dev/schedules/cancel \
-H "Authorization: Bearer $COMMERCE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: cancel-schedule-kPvqTrqGsopu07wf" \
-d '{
"schedule_id": "sch_kPvqTrqGsopu07wfC7ttoWqmfwt48ZW7BGvYUWk"
}'
Common cancellation scenarios: customer opts out of marketing before campaign sends, you discover an error in the message content, or a scheduled event gets postponed. Cancel the original schedule and create a new one with corrected details.
Cancellation only works on pending schedules. Repeating a new cancellation after the schedule is canceled returns an error. A network retry is safe only when it reuses the same Idempotency-Key, which replays the original cancellation result.
Using idempotency keys
Prevent duplicate schedules during network failures or retries by providing request_meta.idempotency_key or the Idempotency-Key header. If your server crashes after scheduling but before recording the schedule ID, retry with the same key—the API returns the original schedule instead of creating a duplicate.
const idempotencyKey = 'ship_notif_order_12345'
const payload = {
recipients: customerPhones,
full_message: 'Your order ships tomorrow',
send_after: tomorrowAt9AM,
request_meta: {
idempotency_key: idempotencyKey,
},
}
async function createSchedule() {
const response = await fetch('https://api.zebo.dev/chimes/schedule', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.COMMERCE_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey,
},
body: JSON.stringify(payload),
})
if (!response.ok) {
throw new Error(`Commerce API request failed: ${response.status}`)
}
return response.json()
}
// First attempt succeeds, but the connection fails before the ID is saved.
await createSchedule()
// Retrying the identical request with the same key returns the first schedule.
const { scheduled_chime } = await createSchedule()
// Save schedule ID to your database
await db.orders.update(orderId, {
notification_schedule_id: scheduled_chime.id,
})
Construct idempotency keys from stable identifiers like order IDs, campaign IDs, or subscription IDs plus the notification type—ship_notify_order_${orderId} or renewal_remind_sub_${subId}.
Next steps
- Chime API Reference — Complete endpoint documentation including schedule lookup
- Send customer notifications — Send immediate notifications for real-time events
- Product: Chime — Deep dive into Chime features and architecture