WF-N1 · No-Show Detection and Marking
Why this workflow exists: every other workflow in this build depends on a reliable no-show event. Built by hand, that event is created inconsistently or not at all. This workflow creates it from the absence of data rather than the presence of it, which is the only way it happens every time.
Trigger: scheduled check running every 5 minutes against all appointments whose appointment_start_at is in the past 4 hours.
Conditions (all must be true to mark a no-show):
- arrival_recorded_at is empty.
- Appointment status is not Canceled, Rescheduled or Completed.
- Now is later than appointment_start_at plus the grace period for this appointment_type: suggested defaults: 10 minutes for short service slots, 15 minutes for consultations, 20 minutes for anything requiring travel to site.
- The appointment was not created inside the grace window itself, which otherwise produces false positives on walk-ins.
Actions, in order:
- Set appointment status to No-Show and stamp no_show_marked_at = now.
- Increment no_show_count_90d and no_show_count_lifetime; set last_no_show_at = now.
- Add tag no-show and no-show-recovery-open. Move record to Marked No-Show.
- Assign recovery_owner: the provider who was due to see them, or the front desk user on shift, by round robin. Never leave this empty.
- Release the slot to the waitlist calendar and fire WF-N7.
- If no_show_count_90d is 2 or more, add no-show-repeat and set reliability_flag = Deposit Required; if exactly 1, set reliability_flag = Watch.
- Post to the internal channel: "No-show: {{contact.first_name}}, {{appointment_type}} with {{provider_name}} at {{location_name}}, {{appointment_start_at}}. Slot released. Owner: {{recovery_owner}}."
- Continue to WF-N2.
Exit and safeguards: if an arrival is recorded after marking (a genuinely late customer) a reverse workflow fires: undo the no-show, decrement the counters, remove the tags, cancel every pending step in WF-N2 and WF-N4, and restore reliability_flag. Build this. Without it, your late arrivals get apology texts for missing an appointment they are sitting in.
WF-N2 · Immediate Recovery Reach-Out
Trigger: record enters Marked No-Show.
Conditions: no do-not-contact, sms-stop or do-not-rebook tag; a consented channel exists; recovery_attempt_count is less than 5.
Actions, in order:
- Wait 1 minute. Just enough to let a manual correction land before the message goes out.
- Branch on channel. SMS consent → SMS. No SMS consent but valid email → email. Neither → create a call task for recovery_owner due in 30 minutes and exit.
- Branch on quiet hours. If now is outside 08:00–20:00 local, hold until 08:00. A no-show at 7:50pm gets its first message the next morning, not at ten past eight at night.
- Send SMS: "Hi {{first_name}}, we had you down for {{appointment_time}} today with {{provider_name}} and missed you. Everything okay? No problem at all if something came up. One tap tells us what happened and gets you a new time: {{reason_link}}"
- Email variant: subject "We missed you today". Same blameless framing, two paragraphs, the six reason buttons inline, then one link to availability.
- Increment recovery_attempt_count; stamp last_recovery_contact_at; move to Recovery In Progress.
- Wait 2 hours. Condition: no reply, no reason captured, no rebooking. Send second SMS, this time with real times in the body: "No rush {{first_name}}, I've got {{slot_1}} or {{slot_2}} free this week if either suits. Just reply with the day and I'll hold it."
- Increment recovery_attempt_count. Hand the thread to BO for reply handling.
Copy rules that matter more than the sequence: never lead with the fee, never use the word "failed", never say "you did not attend", and never ask for an explanation as a condition of rebooking. The first message exists to reopen a conversation, not to settle an account. The fee conversation, where one exists, belongs in WF-N6 and belongs to a human.
Exit conditions: reason captured; rebooking created; inbound reply received; any opt-out tag; reliability_flag set to Do Not Rebook; recovery_attempt_count reaches 5.
WF-N3 · Reason Capture and Branch
Why it matters: the reason is the whole asset. Recovery rates vary enormously by reason, and so do the fixes: "forgot" is a reminder problem, "transport" is an information problem, "cost concern" is a sales problem, and "booked elsewhere" is a speed problem. Without this field you will spend a year sending the same message to four different problems.
Trigger: reason picker tapped, or BO classifies an inbound reply into a reason.
Actions:
- Write no_show_reason, no_show_reason_free_text where given, and no_show_reason_captured_at. Capture on tap, not on submit.
- Cancel all pending steps in WF-N2 and WF-N4. This is the most-missed step in builds like this and the cause of most "why are you still texting me" complaints.
- Add tag no-show-reason-captured; move to Reason Captured.
- Branch on reason:
- Forgot or Wrong time in calendar → straight to the rebook page; set reliability_flag = Watch; flag the contact for the hardened confirmation treatment in WF-N5.
- Transport → rebook page plus an automatic follow-up containing the address, parking, public transport notes and a map link.
- Work or childcare → rebook page filtered to first-of-day, last-of-day and weekend availability only. Offering the same mid-morning slot again is why this reason repeats.
- Illness → suppress all recovery messaging for 72 hours, send one short "get well, no rush" message, then offer times. Never charge a fee on this branch automatically; route it to a human.
- Cost concern → no rebook page. Create a task for recovery_owner due in 4 hours for a human conversation, and send "Totally understand. Let me get {{owner_first_name}} to give you a call so we can talk through the options properly."
- Booked elsewhere → one honest message: "Completely fine, {{first_name}}, thanks for telling us. If it doesn't work out we'll be here." Set reliability_flag unchanged, add to the 90-day win-back audience, exit the build.
- Changed mind → one value-led message with a single soft offer of a short no-obligation call, then exit to nurture.
- No reason given → continue to WF-N4.
Exit: every branch terminates in one of rebooked, human-owned, or nurture. No branch loops back into the ladder.
WF-N4 · The Recovery Ladder
Trigger: record is in Recovery In Progress with no reply after WF-N2's second message.
Global conditions checked before every step: no rebooking exists, no reason captured, no opt-out tag, recovery_attempt_count under 5, quiet hours respected, and last_recovery_contact_at more than 12 hours ago.
Actions, in order:
- Wait until 10:00am the next day. Send SMS, attempt 3: "Morning {{first_name}}, still happy to get you booked in for your {{appointment_type}}. Want me to find you a time this week? Reply YES and I'll send two options."
- Wait 1 day. Branch on appointment_type and preferred_channel. For high-value types, or where preferred_channel = Call: place a voice AI call between 11:00 and 16:00 local. Script: identify the business, say the appointment was missed and that there is no issue, offer two specific times, offer transfer to a person, end inside 45 seconds. Everything else: send email, attempt 4, subject "Still want to get this sorted?", with a three-line body and one availability button.
- On an unanswered call: leave a voicemail, then send the SMS version of the same offer. Never leave a second voicemail.
- Wait 3 days. Attempt 5, the last one, and say so plainly: "Last one from me, {{first_name}}, I don't want to keep bothering you. If you'd still like to come in, book any time here: {{booking_link}}. Otherwise no problem at all and we'll leave it with you."
- Wait 2 days. No response → add no-show-unreachable, move to Unreachable, remove no-show-recovery-open, add to the quarterly win-back audience, close the owner's task with an outcome note.
Stop conditions: any inbound reply of any kind; any rebooking; reason captured; STOP or unsubscribe; recovery_attempt_count reaches 5. The cap is enforced by the field, not by counting steps. Otherwise a re-entry from a second no-show restarts the count and someone gets ten messages.
WF-N5 · Rebooking and Confirmation Hardening
Why: a rebooked no-show is statistically the most fragile appointment on your calendar. Treating it exactly like a first-time booking is how you get the same empty slot twice.
Trigger: new appointment created for a contact carrying the no-show tag.
Actions:
- Write rebooked_appointment_at; calculate rebook_lead_time_hours; add no-show-rebooked; move to Rebooked; close the recovery task.
- Condition: if rebook_lead_time_hours is greater than 168 (more than a week out) add the contact to the extended confirmation track. Long lead times are where recovered bookings die.
- Immediate confirmation SMS: "You're booked, {{first_name}}, {{appointment_type}} with {{provider_name}}, {{appointment_time}}, {{location_name}}. Here's the address and parking: {{location_link}}. Reply C to add it to your calendar."
- Send calendar invite by email with the location, duration and what to bring.
- Wait until 48 hours before. Send reminder with a single question: "Still good for {{appointment_time}} on {{day}}? Reply Y to confirm or R to move it."
- Wait until 24 hours before. Condition: no Y received. Send second reminder and create a task for the front desk to call during the working day. A human call to an unconfirmed recovered booking is the highest-yield phone call in this entire build.
- Wait until 2 hours before. Condition: still no confirmation. Send final reminder: "We're all set for you at {{appointment_time}}, see you shortly. If something's changed, reply R and we'll move it, no problem." Simultaneously add the slot to the waitlist calendar as at risk, so a standby can be warmed up without being promised anything.
- On arrival: stamp arrival_recorded_at, move to Rebooked and Attended, add no-show-recovered, and schedule reliability_flag to reset to Reliable after 90 days with no further no-show.
- On a second no-show: WF-N1 fires again, no_show_count_90d reaches 2, and the record is routed to WF-N6 instead of back into the ladder.
Exit: attended, canceled with notice, or no-show again.
WF-N6 · Repeat No-Show Policy and Deposit Gate
Trigger: no_show_count_90d reaches 2, or reliability_flag set to Deposit Required.
Conditions and branches:
- Second no-show with a reason captured both times → one more chance, but only into a first-of-day or last-of-day slot, with the hardened confirmation track and a card on file.
- Second no-show, no contact either time → deposit gate applies before any booking can be made.
- Third no-show in 90 days → set reliability_flag = Do Not Rebook, move to that stage, notify the owner, and stop all automated outreach. A human decides whether to re-admit them, and that decision is logged in a note.
Actions on the deposit branch:
- Set deposit_required = true; add tag deposit-required; move to Deposit Required.
- Disable the open booking link for this contact so the gate cannot be walked around.
- Send the policy message: written once, in plain words, approved by the owner, and never improvised: "Hi {{first_name}}, we'd still love to see you. Because we've held two slots that went unused, we now take a small deposit to confirm the booking. It comes straight off your bill on the day, and it's fully refundable if you give us 24 hours' notice. Here's the link to book and confirm: {{deposit_booking_link}}"
- Stamp deposit_link_sent_at. Wait 3 days. No action → one reminder. Wait 7 days. Still nothing → move to Unreachable and stop.
- On payment: stamp deposit_paid_at, add deposit-paid, unlock availability, and run WF-N5 with the full hardened track.
- On attendance: apply the deposit to the bill, reset reliability_flag to Watch, and remove the gate.
Non-negotiables: the amount, the refund window and the application of the deposit are commercial decisions for the business and must be settled, written down and reviewed by the client's own adviser before launch, including whether a deposit or fee is permissible at all in the relevant sector and jurisdiction. Never let an automation levy a charge against a stored card without an explicit, separately captured authorization for exactly that. Never apply this branch automatically to the Illness branch of WF-N3.
WF-N7 · Same-Day Slot Refill from Waitlist
Why: the recovery sequence protects the customer relationship. This workflow protects today's revenue, and it is the one that pays for the build.
Trigger: slot released by WF-N1, or a cancellation inside the notice window.
Conditions: remaining slot duration is at least the minimum bookable length; the slot starts more than 45 minutes from now; the provider has not already been reassigned.
Actions, in order:
- Build the audience: contacts with waitlist_opt_in = true, matching appointment_type, matching provider_name where the waitlist entry specified one, whose waitlist_notice_window permits today, and who hold no do-not-rebook or opt-out tag. Order by how long they have waited.
- Send to the first 5 only: "{{first_name}}, a slot just opened with {{provider_name}} today at {{slot_time}}. First to reply YES gets it." Sending to the whole list produces five disappointed people and one booking.
- Wait 8 minutes. No YES → send to the next 5. Repeat up to three rounds, then stop.
- First YES wins: book immediately, confirm in the same thread, set slot_refilled = true, calculate slot_refilled_minutes from no_show_marked_at.
- Every later YES gets an immediate, specific apology and a concrete alternative: "That one's gone, sorry: genuinely quick. I can do {{alt_slot_1}} or {{alt_slot_2}}, or I'll keep you first in line for the next opening." Never leave a late YES unanswered; that is how you lose a waitlist.
- Post the outcome to the internal channel so the front desk stops working the phone for that slot.
Exit: slot filled; slot start time within 45 minutes; three rounds exhausted.
WF-N8 · Late Cancellation Handling
Why it is separate: a customer who cancels four hours out behaved well. A customer who said nothing did not. Collapsing the two into one tag destroys both your reporting and your tone.
Trigger: appointment canceled, with appointment_start_at less than the notice window away: 24 hours is the usual setting.
Actions:
- Stamp late_cancel_at; add tag late-cancel. Do not increment no_show_count_90d.
- Release the slot and fire WF-N7 immediately.
- Send: "Thanks for letting us know, {{first_name}}, no problem at all. Want me to get you back in? {{booking_link}}"
- Wait 1 day. No rebooking → one reminder with two concrete times. Wait 4 days. Still nothing → add to the win-back audience and exit.
- Track late cancellations as their own series. A rising late-cancel rate alongside a falling no-show rate is the build working exactly as intended, and it will look like a problem on any dashboard that counts them together.
Exit: rebooked, or two attempts made.
WF-N9 · Daily At-Risk Review and Weekly Reporting
Trigger: scheduled, 08:15 each working day, and 08:00 each Monday.
Daily actions:
- Build the at-risk list for today: appointments with no confirmation reply, plus every contact whose reliability_flag is Watch or Deposit Required.
- Post it to the internal channel and create one call task per record for the front desk, due before the appointment.
- List yesterday's no-shows still in Recovery In Progress with no reply, assigned by owner, so nothing silently ages.
Weekly actions:
- Email the owner a single digest: no-show rate by appointment_type, by provider_name, by location_name and by day of week and time of day; reason distribution; recovery rate; median slot_refilled_minutes; slots never refilled.
- Flag any provider, slot time or appointment type whose no-show rate is more than 1.5 times the account average, because that is an operational problem a recovery sequence cannot fix.
Exit: runs indefinitely.