Appointment reminders
Reminders that get read and answered, with the confirm-or-move question that turns a reminder into a rebooking.
Why text and not email
A reminder only works if it is seen before the appointment. Email reminders sit unread in a promotions tab; a text is read within minutes. The measurable difference is not the reminder, it is the reply: a text reminder gets answered, so you find out about a conflict the day before instead of at the appointment time.
Consent
Reminders for an appointment the person booked with you are transactional, sent under prior express consent, which is a lower bar than marketing. Do not attach an offer to them. The moment a reminder carries a promotion it becomes a marketing message and needs marketing consent, per the compliance guide.
The build
Two sends per appointment: 24 hours before, and 2 hours before.
// scheduled job
for (const appt of await crm.appointmentsStartingIn({ hours: 24, window: 15 })) {
await send(appt, `Hi ${appt.firstName}, it's Sam from Northside. You're booked in for ${appt.timeLabel} tomorrow with ${appt.staffName}. Still good, or want to move it?`);
}
for (const appt of await crm.appointmentsStartingIn({ hours: 2, window: 15 })) {
if (appt.confirmed) continue;
await send(appt, `Hi ${appt.firstName}, see you at ${appt.timeLabel} today. ${appt.locationHint}`);
}
async function send(appt, content) {
await fetch("https://api.bluereacher.com/v1/messages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BLUEREACHER_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
to: appt.phone,
message: content,
send_mode: "instant",
metadata: { crm_id: appt.recordId, recipe: "appointment-reminder" }
})
});
}The question that does the work
"Still good, or want to move it?" is the whole recipe. A reminder that only informs gets no reply and changes nothing. A reminder that asks gets one of three useful answers: yes (confirmed, and now they have committed out loud), no (you recover the slot with a day's notice instead of losing it), or silence (a signal worth acting on with the 2-hour message).
Handle the reply:
if (e.event === "message.received") {
const text = e.data.content.toLowerCase();
const recordId = await crmIdForPhone(e.data.phone_number);
if (/^(yes|yep|yeah|confirmed|see you|👍)/.test(text)) {
await crm.update(recordId, { confirmed: true });
} else {
await notify(`Appointment reply needs a human: "${e.data.content}"`);
}
}Auto-confirm only on unambiguous replies. Anything else goes to a person, because "yes but can we do 3 instead" is not a confirmation and a bot that treats it as one causes the no-show it was meant to prevent.
Include what they need to show up
Address, floor, parking, what to bring, who to ask for. A reminder that says only the time assumes the person still has the booking email, and many do not. One extra line here removes a real cause of no-shows.
Do not over-send
Two reminders. A third is noise, and reminders go to your best relationships, which are exactly the ones not worth annoying. If the appointment is more than a week out, add one confirmation at booking time and keep the 24-hour and 2-hour pair.
What to measure
No-show rate before and after, confirmation reply rate on the 24-hour message, and how many appointments get moved rather than missed. Moved appointments are wins, not losses, and reporting them as losses hides the value of the whole build.

