Recipes: appointments & local service
Three pipelines for anyone who runs on bookings — clinics, salons, coaches, consultants. They start
from Calendly (Acuity users swap the acuity-trigger) and turn
each booking into the follow-through that keeps clients showing up and coming back.
No-show reducer
The instant someone books, send a warm confirmation with the details and an easy reschedule path — the two things that turn a would-be no-show into a reschedule.
The shape:
booked (calendly-trigger)
└─ booked-gate (filter: invitee.created)
├─ confirm-client (email)
└─ staff-heads-up (slack)A calendly-trigger fires on every Calendly event (created, canceled, no-show), so the
first child is a filter that keeps only new bookings.
# jobs/scheduling/booked.yaml
id: booked
name: New Calendly booking
group: scheduling
type: calendly-trigger
deliveryMode: webhook
credential: calendly
hookId: calendly_hook_0123456789abcdef
schedule: []# jobs/scheduling/booked-gate.yaml
id: booked-gate
name: Only new bookings
group: scheduling
type: filter
input: '{{ $parent.json.event }}'
operator: equals
value: invitee.created
schedule:
- { kind: afterParent, parent: booked, on: succeeded }# jobs/scheduling/confirm-client.yaml
id: confirm-client
name: Confirm to client
group: scheduling
type: email
to: '{{ $parent.json.payload.email }}'
subject: "You're booked — see you soon, {{ $parent.json.payload.name }}"
body: |
Hi {{ $parent.json.payload.name }},
You're confirmed for {{ $parent.json.payload.scheduled_event.start_time }}.
Can't make it? Just reply and we'll find a better time — no problem at all.
schedule:
- { kind: afterParent, parent: booked-gate, on: succeeded, branch: pass }# jobs/scheduling/staff-heads-up.yaml
id: staff-heads-up
name: Staff heads-up
group: scheduling
type: slack
credential: slack_bot
channel: '#front-desk'
message: 'New booking: {{ $parent.json.payload.name }} — {{ $parent.json.payload.scheduled_event.start_time }}'
schedule:
- { kind: afterParent, parent: booked-gate, on: succeeded, branch: pass }Prefer a text? If you collect a mobile number, swap the confirmation to an
smsjob. Keep the timed 24h reminder in Calendly/Acuity itself.
New-client onboarding
Turn every booking into a CRM contact, an onboarding checklist, and a welcome email with your intake form — no manual data entry after each signup.
The shape:
signup (calendly-trigger)
└─ signup-gate (filter: invitee.created)
├─ crm-contact (hubspot)
├─ onboarding-task (clickup)
└─ welcome-email (email)# jobs/onboarding/signup.yaml
id: signup
name: New client signup
group: onboarding
type: calendly-trigger
deliveryMode: webhook
credential: calendly
hookId: onboarding_hook_0123456789abcd
schedule: []# jobs/onboarding/signup-gate.yaml
id: signup-gate
name: Only new bookings
group: onboarding
type: filter
input: '{{ $parent.json.event }}'
operator: equals
value: invitee.created
schedule:
- { kind: afterParent, parent: signup, on: succeeded }Upsert the client by email so a repeat booking never creates a duplicate:
# jobs/onboarding/crm-contact.yaml
id: crm-contact
name: Create CRM contact
group: onboarding
type: hubspot
credential: hubspot
operation: updateContact
contactId: '{{ $parent.json.payload.email }}'
idProperty: email
email: '{{ $parent.json.payload.email }}'
firstname: '{{ $parent.json.payload.name }}'
schedule:
- { kind: afterParent, parent: signup-gate, on: succeeded, branch: pass }# jobs/onboarding/checklist.yaml
id: onboarding-task
name: Onboarding checklist
group: onboarding
type: clickup
credential: clickup
operation: createTask
listId: '{{ $vars.onboardingListId }}'
taskName: 'Onboard {{ $parent.json.payload.name }}'
description: |
New client {{ $parent.json.payload.name }} ({{ $parent.json.payload.email }}).
[ ] Send welcome pack [ ] Collect intake form [ ] Add to billing [ ] Book kickoff
schedule:
- { kind: afterParent, parent: signup-gate, on: succeeded, branch: pass }# jobs/onboarding/welcome.yaml
id: welcome-email
name: Welcome email
group: onboarding
type: email
to: '{{ $parent.json.payload.email }}'
subject: 'Welcome, {{ $parent.json.payload.name }} — one quick step'
body: |
Welcome aboard! Before we meet, please fill out your intake form: {{ $vars.intakeFormUrl }}
It takes two minutes and helps us make the most of our first session.
schedule:
- { kind: afterParent, parent: signup-gate, on: succeeded, branch: pass }Aftercare follow-up chain
After a visit, run a timed sequence — a day-1 thank-you, a day-3 check-in, a day-7 rebook nudge — as
one durable chain. A wait parks the run for free and survives daemon restarts,
and the parent’s data passes straight through it.
The shape:
visit-done (webhook) → thanks (email) → wait 2d → check-in (email) → wait 4d → rebook (email)# jobs/aftercare/visit-done.yaml — your POS/booking tool POSTs { email, name } when a visit completes
id: visit-done
name: Visit complete
group: aftercare
type: webhook
hookId: visit_done_0123456789abcdef01# jobs/aftercare/thanks.yaml
id: aftercare-thanks
name: Thank-you
group: aftercare
type: email
to: '{{ $parent.json.email }}'
subject: 'Thanks for coming in, {{ $parent.json.name }}!'
body: |
It was great to see you. Reply any time if anything comes up — we're here to help.
schedule:
- { kind: afterParent, parent: visit-done, on: succeeded }# jobs/aftercare/pause-1.yaml
id: aftercare-pause-1
name: Wait 2 days
group: aftercare
type: wait
duration: 2d
schedule:
- { kind: afterParent, parent: aftercare-thanks, on: succeeded }# jobs/aftercare/checkin.yaml — parent data still readable through the wait
id: aftercare-checkin
name: Day-3 check-in
group: aftercare
type: email
to: '{{ $parent.json.email }}'
subject: 'How are you doing, {{ $parent.json.name }}?'
body: 'Just checking in a few days on — how are you feeling? Reply and let us know.'
schedule:
- { kind: afterParent, parent: aftercare-pause-1, on: succeeded }# jobs/aftercare/pause-2.yaml
id: aftercare-pause-2
name: Wait 4 days
group: aftercare
type: wait
duration: 4d
schedule:
- { kind: afterParent, parent: aftercare-checkin, on: succeeded }# jobs/aftercare/rebook.yaml
id: aftercare-rebook
name: Day-7 rebook offer
group: aftercare
type: email
to: '{{ $parent.json.email }}'
subject: 'Ready for your next visit?'
body: 'Time for a follow-up? Book whenever suits you: {{ $vars.bookingUrl }}. Hope to see you soon!'
schedule:
- { kind: afterParent, parent: aftercare-pause-2, on: succeeded }