Appointment reminders end to end
An appointment reminder is the archetype of transactional messaging: a one-to-one send whose timing, template, and reply handling all matter, and whose consent came from the appointment itself — not from a campaign. The pieces each have their own page. This guide stitches them into one workflow: schedule → template → reply trigger → DLR webhook → fallback → ledger verification. Everything below is tenant-owned configuration. The reminder’s timing gates, its fallback chain, and its consent posture are controls you set; the platform applies them and never mandates a regulatory gate on your behalf.0. The shape of the flow
- Class and template — mark the send
category: "transactional"so campaign-side controls (frequency caps that list onlymarketing, campaign quiet-hours fallbacks) leave it alone by design. - Schedule —
POST /api/v1/messages/smswithscheduled_atand the appointment-relative fire time. The message parks until the platform scheduler dispatches it. - Reply handling — a keyword rule on
YES/NO(or a flow trigger) turns the recipient’s confirmation into a booking update, no agent required. - No Inbox noise — the transactional no-inbox gate keeps the reminder from minting a conversation per destination.
- DLR webhooks —
message.delivered/message.failedevents, signature-verified, drive your “reminder landed” state machine. - Fallback — an SMS → WhatsApp hop (per-message cascade or campaign ladder) raises the delivery floor.
- Timing gates — quiet hours and frequency caps interact with the scheduled fire time; know which ones touch transactional traffic and which do not.
- Verification — the order-notifications ledger reads the reminder class’s delivery rate, time-to-first-attempt, and failure breakdown.
1. Pick the transactional class and template
Mark the messagecategory: "transactional" and build its body from a template whose variables are appointment fields — recipient name, date, time, location, reply instruction.
- Frequency caps — a cap whose
applies_to_categorieslists only["marketing"]never gates this send. Without the category stamp, a blanket (keyless) cap can reject a reminder that collided with the same contact’s marketing receipts. See Frequency caps. - Campaign-side windows — campaign quiet-hours fallbacks and send-window optimization exist for bulk traffic; the transactional stamp keeps the reminder out of that classification path entirely.
- Ledger attribution — the order-notifications ledger groups machine-triggered transactional traffic separately from campaign reports; marking the class correctly is what makes section 8’s readback coherent.
category field on the send body is the stamp.
2. Schedule the reminder with scheduled_at
Pass the appointment-relative fire time on the send endpoint. The API answers 202 and the message parks in scheduled until the platform scheduler promotes it — see Schedule one-off sends with scheduled_at for the full surface (edit, cancel, inspect).
Z or offset — never an offset-less datetime-local string, which reinterpreted as UTC shifts the fire time. Until the row promotes to queued you can PATCH /api/v1/messages/{id} (appointment moved → new scheduled_at) or POST /api/v1/messages/{id}/cancel (appointment cancelled) — both free, both gated on the row still being scheduled.
Do you want send_at_type?
send_at_type: "optimize_with_send_window" treats scheduled_at as the earliest acceptable time and shifts out-of-window sends to the next window open. For reminders, use fixed (the default): a reminder whose value depends on landing 24 h before the appointment must not slide to the next morning because a send-window knob moved it. The optimization mode is the right choice for compliance-sensitive marketing traffic, not for this workflow. See Send-window optimization with send_at_type.
3. Route confirmation replies into a flow
The reminder invitesYES / NO. Handle those replies without opening an agent thread: keyword auto-reply rules watch the inbound and execute the moment one matches — send the acknowledgement and trigger a flow that updates the booking.
YES(exact match) — replyConfirmed — see you tomorrow at 10:15.and trigger a confirmation flow that writes the status back to your booking system.NO(exact match) — reply a reschedule link or trigger a reschedule flow that offers new slots.
YES/NO (any letter case), and the catch-all — a regex .* rule created last — only fires when nothing earlier claimed the reply, so a recipient who answers with a sentence instead of the keyword still lands somewhere sensible (forward to an agent, or into a triage flow). If the reply starts a flow, the flow’s trigger is the keyword event and its nodes do the booking update — see Build your first automation flow for trigger payload and node shape.
4. Opt into the transactional no-inbox gate
By default every outbound send upserts an Inbox conversation and rings the new-conversation bell on a destination’s first appearance. For a reminder fired at thousands of fresh destinations that is one bell per recipient — none of which have anything to answer. The transactional no-inbox gate suppresses the conversation upsert while preserving the full Messages row and DLR trail. Opt in explicitly on the termination rule withinbox: "none" on the deliver hop when you terminate reminders through one; for WhatsApp AUTHENTICATION-category templates the gate infers transactional status without a rule. If the recipient replies, the inbound path opens the thread then — when there is something to answer — and the keyword rules in step 3 have already claimed the confirmations that matter.
5. Consume DLR webhooks, signatures verified
Subscribe tomessage.sent, message.delivered, message.failed (and message.read where the channel returns it) and advance your booking-side “reminder status” state machine on each event. The event payload’s id is the message-row id your send returned — correlate on it.
Verify every incoming webhook before acting on it: read the raw body, check X-Orbit-Signature (with X-Devotel-Signature as fallback for old queued deliveries), and answer 401 on failure. The one-call-per-language verifiers are in Verify webhook signatures with the SDK; the no-SDK path is in Webhook signature verify polyglot. Event semantics are at-least-once — a late genuine DLR corrects a row the no-receipt safety net aged, and your consumer must treat corrections as normal traffic. See DLR outcomes monitoring for which WARN classes that path emits and what they mean.
6. Add an SMS → WhatsApp fallback hop
If SMS delivery matters more than SMS cost, chain a fallback: when the SMS hop fails (or produces no delivery report in your window), advance to WhatsApp. For one-shot transactional traffic use the per-message cascade policy on/messages/smart-send; the campaign channels[] ladder is for bulk sends and the Verify profile chain is for OTP. Ordering rules, failure semantics (failed vs no_delivery vs no_engagement), and per-hop billing are in Fallback chains.
Two bounds to set on any chain: max_price (skip a hop whose projected price exceeds the cap) and max_total_price (trim the waterfall at a cumulative budget). Every hop is billed independently, so a chain without a declared ceiling is a cost surprise waiting for the first carrier outage.
7. How quiet hours and frequency caps interact with the scheduled fire time
Scheduling changes when a send fires; the gates it must pass at fire time are unchanged. Layered, they resolve like this:- Org-wide quiet-hours gate — when you’ve enabled a channel gate (Settings → Compliance → Quiet hours, or
PATCH /api/v1/settings/compliance/quiet-hours), a scheduled send whose fire time lands inside the window re-parks to the next allowed instant at fire time on channels where re-park applies. That is usually the right behavior for a reminder — it slides to 9 AM rather than firing at 3 AM. If you need the reminder to fire verbatim regardless, leave the gate off for that channel (your call — the gate is opt-in per channel). See Quiet hours: org-wide channel gates vs. the campaign fallback window. - Campaign fallback window — does not touch this workflow. It applies to drip and journey sends dispatched through campaign rails, and a bare
POST /api/v1/messagesreminder is not campaign traffic. - Frequency caps — only gate the send when the cap’s
applies_to_categoriesis absent (blanket) or includes the category on the send. Withcategory: "transactional"set (step 1), a marketing-scoped cap cannot touch the reminder; a blanket cap can. Scope your caps deliberately, or size the blanket cap so a day’s reminder volume per contact can never reach the ceiling. See Frequency caps. - DST and timezone — pass
recipient_timezonewhen you know it and let the platform resolve the recipient-local window; DST gaps and folds shift the dispatch rather than skipping it. See Recipient timezone resolution.
8. Verify outcomes in the order-notifications ledger
The order-notifications ledger is the reading surface for machine-triggered transactional traffic — reminder traffic included, because the class stamp from step 1 is what routes these sends into the transactional reading rather than campaign reports. Over your reminder window the ledger header computes:- Delivery rate — delivered (plus read where returned) over terminal sends, in-flight excluded.
- Time-to-first-attempt — send-to-first-delivery-signal lag per channel; a rising number on the SMS hop is the case for the fallback chain in step 6.
- Channel mix — which hop actually carried the send against the intended first choice.
- Failure breakdown — rejected vs undelivered vs failed, grouped by rejection code, so recipient-side hygiene (
undelivered) separates from dispatch/configuration faults (rejected).
/api/v1/analytics/messages with the channel and status filters for a scheduled BI read. A reminder-stage that silently stopped firing shows as a missing series, not a drop in volume — that distinction is the first thing to check when confirmations stop arriving.
Related
- Schedule one-off sends with
scheduled_at— the parking surface, edit/cancel semantics, and accepted formats. - Send-window optimization with
send_at_type— the optimization mode you deliberately skip for reminders. - Keyword auto-reply rules —
YES/NOmatching, flow triggers, and the catch-all. - Keep one-time codes out of the Inbox — the no-inbox gate mechanics.
- Verify webhook signatures with the SDK — per-language verification handlers.
- Fallback chains — ordering rules, failure semantics, per-hop cost bounds.
- Quiet hours: org-wide channel gates vs. the campaign fallback window — which windows touch which traffic classes.
- Frequency caps — category scoping so the reminder never hits a marketing-shape ceiling.
- Order notifications ledger — the verification surface.
- DLR outcomes monitoring — reading the WARN classes the no-receipt path emits.