Blue Reacher
Appointment reminders50 / 50

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.

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.

On this page