RecipesAppointments & local service

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 sms job. 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 }