# Origamy playbook spec v1. Reference: /playbooks/spec-format/
spec_version: 1
id: beauty.booking-reminders
title: Booking reminders for salons and studios
sector: beauty
source:
  kind: synthesized
  notes: >-
    No salon has built this with us yet. Built from the booking-form reminder pattern:
    a booking form, a booking follow-up that copies the slot time onto the client, and hourly
    segments for the reminders. The cancellation message and the optional WhatsApp versions
    are new.
derived_from: null
vocabulary:
  person: client
  people: clients
  group: team
summary: >-
  Clients book through an Origamy booking form. Each booking gets a confirmation, a reminder the
  day before with your address and phone number, and a short reminder a couple of hours
  before. A cancellation clears the reminders and sends a kind message with your booking link.
when_to_use:
  - You run a salon, barbershop, nail or lash studio, or spa, and no-shows leave gaps in your day.
  - Clients book by phone or WhatsApp today and you want them to book through a link instead.
  - You want reminders by SMS or WhatsApp, not email.

requires:
  data_in:
    - how: booking_form
      what: "The booking form this playbook creates. Put its link in your Instagram bio, your WhatsApp status and your Google profile. Each booking saves its time on the client."
  channels:
    - channel: sms
      for: Confirmation, both reminders and the cancellation message
      needs: An SMS provider, or Origamy managed SMS after business verification (KYC).
    - channel: whatsapp
      for: The same four messages, if you switch WhatsApp on
      needs: A connected WhatsApp Business number, and the four templates approved by Meta.
  events:
    - booking.created
    - booking.cancelled
  traits:
    - "{{ params.appointment_trait }}"

parameters:
  - key: org_name
    ask: true
    question: What should messages call your salon or studio?
    label: Business name
    type: string
    example: Northwind Salon
  - key: salon_phone
    ask: true
    question: Which number should clients call or message to change a booking?
    label: Phone for changes
    type: string
    example: your salon's phone or WhatsApp number
  - key: salon_address
    ask: true
    question: What address or directions should the day-before reminder include?
    label: Address
    type: text
    example: your street, area and a landmark
  - key: services
    ask: true
    question: What can clients book? Give each a short id and the name clients see.
    help: Use the same ids as your services in the service catalog.
    label: Services
    type: list
    item_fields:
      key: { type: string, help: "short id, e.g. braids" }
      name: { type: string, help: "what clients see on the form" }
    default:
      - { key: haircut, name: Haircut }
      - { key: braids, name: Braids }
      - { key: manicure, name: Manicure }
      - { key: facial, name: Facial }
  - key: use_whatsapp
    ask: true
    question: Should reminders go by WhatsApp instead of SMS?
    help: Needs a connected WhatsApp Business number and Meta approval for four templates.
    label: Use WhatsApp
    type: boolean
    default: false
  - key: same_day_hours
    label: Hours before for the second reminder
    type: hours
    default: 2
  - key: appointment_trait
    label: Trait that holds the next appointment time
    type: trait_key
    default: next_appointment_at
  - key: greeting_fallback
    label: Greeting when a first name is missing
    type: string
    default: there
  - key: default_country
    label: Default phone country on the form
    type: country_code
    default: "{{ workspace.country }}"
  - key: timezone
    label: Time zone
    type: timezone
    default: "{{ workspace.timezone }}"

timings:
  - A booking is confirmed straight away, by SMS or WhatsApp.
  - The reminders run every hour.
  - First reminder when the appointment is less than a day away, second when it is less than {{ params.same_day_hours }} hours away. Each client gets each reminder once per appointment.
  - A cancellation clears the saved time, so no reminder goes out, and sends a short message with your booking link.

customer_must:
  - "Set up your services in the service catalog (length of each, your opening hours), and your stylists if clients choose one. Then open the booking form's time slot question and check every service is bound to the right catalog entry. Booking forms can't be published until that is done."
  - Publish the booking form and put its link where clients find you. Tell clients the link is the quickest way to book.
  - "Switch on \"Booking saved\" and \"Booking cancelled\" first: they copy each booking's time onto the client and clear it on a cancellation. The reminders read that time."
  - "Connect an SMS provider or finish business verification. If you chose WhatsApp, connect your WhatsApp Business number and submit the four WhatsApp templates for Meta approval before switching those follow-ups on."
  - Book a test appointment with your own phone, check the confirmation, then switch the two reminder follow-ups on.

objects:
  - ref: form_booking
    kind: form
    about: The booking form. Service, time slot and a phone number; each booking sends booking.created.
    body:
      name: Book an appointment
      slug: book
      description: Pick a service and a time that suits you.
      draft_state:
        version: 1
        meta: { layout: classic, submit_label: Book, success_message: "Thank you! We'll send you a message to confirm your booking." }
        pages:
          - id: p1
            title: Book with {{ params.org_name }}
            description: Choose what you'd like done and when.
            fields:
              - { id: f_first, key: first_name, type: short_text, label: First name, required: true, maps_to: { mode: custom, key: first_name, trait: true } }
              - { id: f_phone, key: phone, type: phone, label: Phone number, required: true, maps_to: { mode: mapped, slot: profile.phone }, config: { country_picker: true, default_country: "{{ params.default_country }}", verify: "off" } }
              - id: f_service
                key: service
                type: single_select
                label: What would you like?
                required: true
                maps_to: { mode: mapped, slot: booking.service }
                options:
                  - $each: params.services
                    $value: { value: "{{ item.key }}", label: "{{ item.name }}" }
              - { id: f_slot, key: slot, type: date_slot, label: Pick a day and time, required: true, maps_to: { mode: mapped, slot: booking.slot } }
              - { id: f_notes, key: notes, type: long_text, label: Anything we should know? (optional), required: false, maps_to: { mode: custom, key: booking_notes } }
      verification_settings: { required: false }

  - ref: tmpl_confirm_sms
    kind: template
    about: Booking confirmation by SMS.
    body:
      name: Booking confirmed (SMS)
      channel: sms
      category: Appointments
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hi {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, you're booked at {{ params.org_name }} for {{ properties.slot_start_ts | date: \"%a %-d %b, %H:%M\" }}. To change it, call {{ params.salon_phone }}. Reply STOP to opt out."
  - ref: tmpl_confirm_wa
    kind: template
    when: { param: use_whatsapp, equals: true }
    about: Booking confirmation on WhatsApp (needs Meta approval).
    body:
      name: Booking confirmed (WhatsApp)
      channel: whatsapp
      category: Appointments
      style: text
      status: active
      subject: ""
      provider_template_id: beauty_booking_confirmed
      metadata: { whatsapp: { category: UTILITY, language: en, samples: { "1": there, "2": "Sat 8 Nov, 10:00" } } }
      variables:
        "1": "{{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}"
        "2": "{{ properties.slot_start_ts | date: \"%a %-d %b, %H:%M\" }}"
      body: "Hi {{1}}, you're booked at {{ params.org_name }} for {{2}}. To change it, call {{ params.salon_phone }}."
  - ref: tmpl_cancel_sms
    kind: template
    about: Kind message after a cancellation, with the booking link.
    body:
      name: Booking cancelled (SMS)
      channel: sms
      category: Appointments
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hi {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, your booking at {{ params.org_name }} is cancelled. Sorry we'll miss you. Rebook any time: {{ ref.form_booking.url }} Reply STOP to opt out."
  - ref: tmpl_cancel_wa
    kind: template
    when: { param: use_whatsapp, equals: true }
    about: Cancellation message on WhatsApp (needs Meta approval).
    body:
      name: Booking cancelled (WhatsApp)
      channel: whatsapp
      category: Appointments
      style: text
      status: active
      subject: ""
      provider_template_id: beauty_booking_cancelled
      metadata: { whatsapp: { category: UTILITY, language: en, samples: { "1": there } } }
      variables:
        "1": "{{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}"
      body: "Hi {{1}}, your booking at {{ params.org_name }} is cancelled. Sorry we'll miss you. You can rebook any time: {{ ref.form_booking.url }}"

  - ref: wf_booked
    kind: workflow
    about: When a booking comes in, save its time on the client and confirm it.
    body:
      name: "Booking saved"
      description: Copies each booking's time onto the client and sends the confirmation.
      enabled: false
      trigger_type: event
      trigger_config: { event_name: booking.created, form_id: "{{ ref.form_booking.id }}" }
      steps:
        - { id: s1, type: action, config: { action_type: set_trait, traits: { "{{ params.appointment_trait }}": "{{ properties.slot_start_ts }}" } } }
        - { id: s2, type: action, when: { param: use_whatsapp, equals: false }, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_confirm_sms.id }}" } } } }
        - { id: s3, type: action, when: { param: use_whatsapp, equals: true }, config: { action_type: send_message, channel_config: { mode: single, single: { channel: whatsapp, template_id: "{{ ref.tmpl_confirm_wa.id }}" } } } }
      re_entry_policy: every_time
      re_entry_cooldown_hours: 0
      goal: {}
      exit_conditions: []
  - ref: wf_cancelled
    kind: workflow
    about: A cancelled booking clears the saved time and sends a kind rebooking message.
    body:
      name: "Booking cancelled"
      description: Clears the saved appointment time when a booking is cancelled and says sorry.
      enabled: false
      trigger_type: event
      trigger_config: { event_name: booking.cancelled, form_id: "{{ ref.form_booking.id }}" }
      steps:
        - { id: s1, type: action, config: { action_type: set_trait, traits: { "{{ params.appointment_trait }}": "" } } }
        - { id: s2, type: action, when: { param: use_whatsapp, equals: false }, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_cancel_sms.id }}" } } } }
        - { id: s3, type: action, when: { param: use_whatsapp, equals: true }, config: { action_type: send_message, channel_config: { mode: single, single: { channel: whatsapp, template_id: "{{ ref.tmpl_cancel_wa.id }}" } } } }
      re_entry_policy: every_time
      re_entry_cooldown_hours: 0
      goal: {}
      exit_conditions: []

  - ref: seg_tomorrow
    kind: segment
    about: Appointment within the next day.
    body:
      name: Appointment within a day
      description: "{{ params.appointment_trait }} within the next 24 hours."
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: "{{ params.appointment_trait }}", cond_operator: within_next, value: 1d }
  - ref: seg_soon
    kind: segment
    about: Appointment within a few hours.
    body:
      name: Appointment within hours
      description: "{{ params.appointment_trait }} within the next {{ params.same_day_hours }} hours."
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: "{{ params.appointment_trait }}", cond_operator: within_next, value: "{{ params.same_day_hours }}h" }

  - ref: tmpl_day_before_sms
    kind: template
    about: Day-before reminder with address and how to change the booking.
    body:
      name: Appointment tomorrow (SMS)
      channel: sms
      category: Appointments
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hi {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, a reminder of your appointment at {{ params.org_name }} on {{ traits.{{ params.appointment_trait }} | date: \"%a %-d %b, %H:%M\" }}. We're at {{ params.salon_address }}. To change it, reply or call {{ params.salon_phone }}. Reply STOP to opt out."
  - ref: tmpl_day_before_wa
    kind: template
    when: { param: use_whatsapp, equals: true }
    about: Day-before reminder on WhatsApp (needs Meta approval).
    body:
      name: Appointment tomorrow (WhatsApp)
      channel: whatsapp
      category: Appointments
      style: text
      status: active
      subject: ""
      provider_template_id: beauty_reminder_day_before
      metadata: { whatsapp: { category: UTILITY, language: en, samples: { "1": there, "2": "Sat 8 Nov, 10:00" } } }
      variables:
        "1": "{{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}"
        "2": "{{ traits.{{ params.appointment_trait }} | date: \"%a %-d %b, %H:%M\" }}"
      body: "Hi {{1}}, a reminder of your appointment at {{ params.org_name }} on {{2}}. We're at {{ params.salon_address }}. To change it, reply here or call {{ params.salon_phone }}."
  - ref: tmpl_same_day_sms
    kind: template
    about: Short reminder a couple of hours before.
    body:
      name: Appointment today (SMS)
      channel: sms
      category: Appointments
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "{{ params.org_name }}: see you at {{ traits.{{ params.appointment_trait }} | date: \"%H:%M\" }} today. Running late or can't come? Call {{ params.salon_phone }}. Reply STOP to opt out."
  - ref: tmpl_same_day_wa
    kind: template
    when: { param: use_whatsapp, equals: true }
    about: Short reminder on WhatsApp (needs Meta approval).
    body:
      name: Appointment today (WhatsApp)
      channel: whatsapp
      category: Appointments
      style: text
      status: active
      subject: ""
      provider_template_id: beauty_reminder_same_day
      metadata: { whatsapp: { category: UTILITY, language: en, samples: { "1": "10:00" } } }
      variables:
        "1": "{{ traits.{{ params.appointment_trait }} | date: \"%H:%M\" }}"
      body: "{{ params.org_name }}: see you at {{1}} today. Running late or can't come? Reply here or call {{ params.salon_phone }}."

  - ref: wf_day_before
    kind: workflow
    about: Hourly run over "within a day"; one reminder per appointment.
    body:
      name: "Appointment reminder: a day before"
      description: Every hour, a reminder to clients whose appointment is less than a day away.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_tomorrow.id }}", rrule: FREQ=HOURLY, starts_at: "{{ workspace.tomorrow }}T07:00", timezone: "{{ params.timezone }}" }
      steps:
        - { id: s1, type: action, when: { param: use_whatsapp, equals: false }, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_day_before_sms.id }}" } } } }
        - { id: s2, type: action, when: { param: use_whatsapp, equals: true }, config: { action_type: send_message, channel_config: { mode: single, single: { channel: whatsapp, template_id: "{{ ref.tmpl_day_before_wa.id }}" } } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 30
      goal: {}
      exit_conditions: []
  - ref: wf_same_day
    kind: workflow
    about: Hourly run over "within hours"; one short reminder per appointment.
    body:
      name: "Appointment reminder: same day"
      description: Every hour, a short reminder to clients whose appointment is a few hours away.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_soon.id }}", rrule: FREQ=HOURLY, starts_at: "{{ workspace.tomorrow }}T07:00", timezone: "{{ params.timezone }}" }
      steps:
        - { id: s1, type: action, when: { param: use_whatsapp, equals: false }, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_same_day_sms.id }}" } } } }
        - { id: s2, type: action, when: { param: use_whatsapp, equals: true }, config: { action_type: send_message, channel_config: { mode: single, single: { channel: whatsapp, template_id: "{{ ref.tmpl_same_day_wa.id }}" } } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 12
      goal: {}
      exit_conditions: []

eval:
  name: 77_beauty_booking_reminders
  mode: task
  vertical: ""
  prompt: >-
    I run a hair salon and clients book through our online booking form. Text each client a
    confirmation when they book, again the day before with our address and the number to call to
    change it, and again two hours before. If someone cancels, send a short sorry message with
    the booking link so they can rebook.
  expect_artifacts: [segment, workflow, message_template]
  forbid_terms: [discount]
