# Origamy playbook spec v1. Reference: /playbooks/spec-format/
spec_version: 1
id: beauty.rebooking-and-reviews
title: Thank-yous, rebooking nudges and win-back for salons
sector: beauty
source:
  kind: synthesized
  notes: >-
    No salon has built this with us yet. Modelled on the base patterns: a staff form matched by
    phone records each visit, a thank-you after the
    visit, daily segments on the last-visit date for rebooking nudges, and a call task for
    clients who have gone quiet. Nothing here needs an app or a developer.
derived_from: base.win-back
vocabulary:
  person: client
  people: clients
  group: team
summary: >-
  Your staff log each visit on a short form. Three hours later the client gets a thank-you with
  your review link. When they are due back, they get a friendly nudge to rebook, and clients
  who haven't been for four months get a personal call from you. A monthly summary shows
  visits and who is slipping away.
when_to_use:
  - You run a salon, barbershop, nail or lash studio, or spa and clients mostly rebook by messaging you.
  - Reviews on Google or Instagram bring you new clients, and you rarely ask for them.
  - You would like to know who hasn't been in for months, without going through your book.

requires:
  data_in:
    - how: staff_form
      what: "The Log a visit form this playbook creates. Staff fill it after each client, in about ten seconds, matched to the client by phone."
  channels:
    - channel: sms
      for: Thank-you, review request and rebooking nudge
      needs: An SMS provider, or Origamy managed SMS after business verification (KYC).
    - channel: whatsapp
      for: The same two messages, if you switch WhatsApp on
      needs: A connected WhatsApp Business number, and the two templates approved by Meta.
    - channel: email
      for: The monthly summary to your team
      needs: Team members with an email address.
  events:
    - onboarding.submitted
  traits:
    - last_visit_at
    - last_service
    - lapsed_call

parameters:
  - key: org_name
    ask: true
    question: What should messages call your salon or studio?
    label: Business name
    type: string
    example: Northwind Salon
  - key: services
    ask: true
    question: Which services should staff pick from when they log a visit?
    label: Services
    type: list
    default: [Haircut, Braids, Colour, Manicure, Facial]
  - key: review_url
    ask: true
    question: Where should happy clients leave a review? (your Google or Instagram link; leave empty to just say thank you)
    label: Review link
    type: url
    default: ""
  - key: booking_url
    ask: true
    question: Where do clients book? (a booking link; leave empty to ask them to reply or call)
    label: Booking link
    type: url
    default: ""
  - key: rebook_after_days
    ask: true
    question: How many days after a visit should clients get the rebooking nudge?
    help: Barbers might say 21, braids 50.
    label: Days until rebook
    type: days
    default: 42
  - key: use_whatsapp
    ask: true
    question: Should these messages go by WhatsApp instead of SMS?
    help: Needs a connected WhatsApp Business number and Meta approval for two templates.
    label: Use WhatsApp
    type: boolean
    default: false
  - key: call_team
    label: Team that calls lapsed clients
    type: team_name
    default: Front desk
  - key: lapsed_after_days
    label: Days without a visit before a client counts as lapsed
    type: days
    default: 120
  - key: thank_you_delay
    label: Thank-you after the visit is logged
    type: duration
    default: 3h
  - key: send_time
    label: Daily send time
    type: time
    default: "10:00"
  - key: monthly_report_cron
    label: When the monthly summary arrives
    type: cron
    default: "0 8 1 * *"
  - 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:
  - Thank-you {{ params.thank_you_delay }} after a visit is logged. A client gets the review link at most every two weeks.
  - Rebooking nudge every day at {{ params.send_time }} for clients whose last visit was more than {{ params.rebook_after_days }} days ago (and under four months). At most one nudge a month.
  - A call task for {{ params.call_team }} for clients whose last visit was over {{ params.lapsed_after_days }} days ago and under a year ago, due in 72 hours. At most one every six months.
  - A summary on the 1st of each month.

customer_must:
  - "Publish the Log a visit form and bookmark it on the till or reception phone. Staff fill it after each client with the phone number the client uses with you. Clients who never get logged never get a message."
  - Add the people who make calls to the {{ params.call_team }} team (it can be just you), and anyone who should get the monthly summary.
  - Only log clients who are happy to be messaged. Ask at the chair, and keep a note somewhere visible.
  - "Connect an SMS provider or finish business verification. If you chose WhatsApp, connect your WhatsApp Business number and submit the two templates for Meta approval first. The WhatsApp thank-you is built around your review link, so give one."
  - Check the segment sizes, log a test visit with your own phone, then switch the follow-ups on.

objects:
  - ref: team_calls
    kind: team
    about: Calls clients who have gone quiet. Can be one person.
    body: { name: "{{ params.call_team }}" }

  - ref: form_visit
    kind: form
    about: Staff form to log a visit. Saves the visit date and service on the client.
    body:
      name: Log a visit
      slug: log-visit
      description: Staff only. One entry per client visit, matched by the client's phone number.
      draft_state:
        version: 1
        meta: { layout: classic, submit_label: Log visit, success_message: Logged. }
        pages:
          - id: p1
            title: Log a visit
            description: Use the phone number the client uses with us.
            fields:
              - { id: f_phone, key: phone, type: phone, label: Client's phone, required: true, maps_to: { mode: mapped, slot: profile.phone }, config: { country_picker: true, default_country: "{{ params.default_country }}", verify: "off" } }
              - { id: f_first, key: first_name, type: short_text, label: Client's first name, required: false, maps_to: { mode: custom, key: first_name, trait: true } }
              - id: f_service
                key: service
                type: multiple_choice
                label: What did they have?
                required: true
                maps_to: { mode: custom, key: last_service, trait: true }
                options:
                  - $each: params.services
                    $value: { value: "{{ item }}", label: "{{ item }}" }
              - { id: f_stylist, key: stylist, type: short_text, label: Who looked after them? (optional), required: false, maps_to: { mode: custom, key: stylist } }
              - { id: f_date, key: visit_date, type: date, label: Visit date, required: true, maps_to: { mode: custom, key: last_visit_at, trait: true }, config: { default_today: true } }
      verification_settings: { required: false }

  - ref: seg_due_rebook
    kind: segment
    about: Last visit more than the rebooking gap ago, but within four months.
    body:
      name: Due to rebook
      description: Last visit between {{ params.rebook_after_days }} and 120 days ago.
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: last_visit_at, cond_operator: older_than, value: "{{ params.rebook_after_days }}d" }
          - { type: trait, trait_key: last_visit_at, cond_operator: within_last, value: 120d }
  - ref: seg_lapsed
    kind: segment
    about: Last visit over the lapsed limit ago, but within a year.
    body:
      name: Lapsed clients
      description: Last visit between {{ params.lapsed_after_days }} days and a year ago.
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: last_visit_at, cond_operator: older_than, value: "{{ params.lapsed_after_days }}d" }
          - { type: trait, trait_key: last_visit_at, cond_operator: within_last, value: 365d }

  - ref: tmpl_thanks_sms
    kind: template
    about: Thank-you after a visit, with a review link if you gave one.
    body:
      name: Thanks for visiting (SMS)
      channel: sms
      category: Feedback
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hi {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, thank you for visiting {{ params.org_name }} today.{% if \"{{ params.review_url }}\" != \"\" %} If you enjoyed it, a quick review helps a small business a lot: {{ params.review_url }}{% endif %} Reply STOP to opt out."
  - ref: tmpl_thanks_wa
    kind: template
    when: { param: use_whatsapp, equals: true }
    about: Thank-you on WhatsApp (needs Meta approval). The review link goes in the text.
    body:
      name: Thanks for visiting (WhatsApp)
      channel: whatsapp
      category: Feedback
      style: text
      status: active
      subject: ""
      provider_template_id: beauty_visit_thanks
      metadata: { whatsapp: { category: MARKETING, language: en, samples: { "1": there } } }
      variables:
        "1": "{{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}"
      body: "Hi {{1}}, thank you for visiting {{ params.org_name }} today. If you enjoyed it, a quick review helps a small business a lot: {{ params.review_url }}"
  - ref: tmpl_rebook_sms
    kind: template
    about: Friendly nudge to book the next visit.
    body:
      name: Time to rebook (SMS)
      channel: sms
      category: Marketing
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hi {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, it's been a while since your {{ traits.last_service | downcase }} at {{ params.org_name }}. Time for the next one?{% if \"{{ params.booking_url }}\" != \"\" %} Book here: {{ params.booking_url }}{% else %} Reply to book your slot.{% endif %} Already booked? Ignore this. Reply STOP to opt out."
  - ref: tmpl_rebook_wa
    kind: template
    when: { param: use_whatsapp, equals: true }
    about: Rebooking nudge on WhatsApp (needs Meta approval).
    body:
      name: Time to rebook (WhatsApp)
      channel: whatsapp
      category: Marketing
      style: text
      status: active
      subject: ""
      provider_template_id: beauty_time_to_rebook
      metadata: { whatsapp: { category: MARKETING, language: en, samples: { "1": there, "2": haircut } } }
      variables:
        "1": "{{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}"
        "2": "{{ traits.last_service | downcase }}"
      body: "Hi {{1}}, it's been a while since your {{2}} at {{ params.org_name }}. Time for the next one? Reply here to book your slot, or ignore this if you've already booked."

  - ref: wf_thanks
    kind: workflow
    about: Thank-you and review request after a visit is logged.
    body:
      name: "Visit: thank you and review"
      description: When a visit is logged, a thank-you with the review link.
      enabled: false
      trigger_type: event
      trigger_config: { event_name: onboarding.submitted, form_id: "{{ ref.form_visit.id }}" }
      steps:
        - { id: s1, type: delay, config: { duration: "{{ params.thank_you_delay }}" } }
        - { 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_thanks_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_thanks_wa.id }}" } } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 336
      goal: {}
      exit_conditions: []
  - ref: wf_rebook
    kind: workflow
    about: Daily run over "due to rebook"; one nudge a month.
    body:
      name: "Rebooking nudge"
      description: Every day, a nudge to clients who are due back.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_due_rebook.id }}", rrule: FREQ=DAILY, starts_at: "{{ workspace.tomorrow }}T{{ params.send_time }}", 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_rebook_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_rebook_wa.id }}" } } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 720
      goal: {}
      exit_conditions: []
  - ref: wf_lapsed
    kind: workflow
    about: Daily run over "lapsed clients"; one personal call task every six months.
    body:
      name: "Lapsed clients: personal call"
      description: Every day, a call task for clients who haven't been in for months. The outcome is saved on the client.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_lapsed.id }}", rrule: FREQ=DAILY, starts_at: "{{ workspace.tomorrow }}T{{ params.send_time }}", timezone: "{{ params.timezone }}" }
      steps:
        - id: s1
          type: human_task
          config:
            human_task:
              title: "Say hello: {{ traits.first_name | default: \"a lapsed client\" }}"
              instructions: |-
                {{ traits.first_name | default: "This client" }} ({{ traits.phone }}) last came in on {{ traits.last_visit_at | date: "%-d %b %Y" }} for {{ traits.last_service | downcase }}.
                Call or message to say hello, ask how they have been and offer a time. Keep it friendly, not pushy. Note anything they tell you.
              assignee_refs: [{ type: team, id: "{{ ref.team_calls.id }}" }]
              strategy: round_robin
              completion_policy: first_response_wins
              outcome_options:
                - { id: rebooking, label: Rebooking, intent: positive }
                - { id: not_now, label: Not now, intent: neutral }
                - { id: not_reached, label: Could not reach, intent: negative }
              due_hours: 72
              wait: true
              wait_timeout_hours: 168
              notify: immediate
        - { id: s2, type: action, config: { action_type: set_trait, traits: { lapsed_call: "{{ properties.task_outcome | default: \"unknown\" }}" } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 4320
      goal: {}
      exit_conditions: []

  - ref: q_visits
    kind: saved_question
    about: How many visits were logged (used by the monthly summary).
    body:
      name: "Report: visits logged"
      data_plane_id: default
      viz_type: number
      viz_settings: {}
      visibility: workspace
      query_ast:
        table: events
        filters:
          operator: and
          conditions:
            - { field: event_name, op: eq, value: onboarding.submitted }
            - { field: properties.form_id, op: eq, value: "{{ ref.form_visit.id }}" }
        aggregations: [{ fn: count, alias: visits }]
  - ref: report_monthly
    kind: report
    about: Last month's visits, and how many clients are due back or lapsed, sent to your team.
    body:
      name: "Monthly: visits and lapsed clients"
      description: Visits logged last month and where clients stand now.
      schedule_cron: "{{ params.monthly_report_cron }}"
      timezone: "{{ params.timezone }}"
      enabled: false
      spec:
        period: previous_month
        compare: previous_period
        week_start: monday
        sections:
          - { id: m_visits, kind: saved_question, title: Visits logged, viz: number, params: { question_id: "{{ ref.q_visits.id }}" } }
          - id: m_clients
            kind: segment_sizes
            title: Clients due back and lapsed
            params:
              segment_ids: ["{{ ref.seg_due_rebook.id }}", "{{ ref.seg_lapsed.id }}"]
          - { id: m_tasks, kind: tasks_summary, title: Calls to lapsed clients, params: {} }
      recipients: [{ type: team, id: "{{ ref.team_calls.id }}" }]
      channel_policy: { mode: email_only }

eval:
  name: 78_beauty_rebooking_and_reviews
  mode: task
  vertical: ""
  prompt: >-
    We're a barbershop. After each visit my staff will log the client on a short form with their
    phone, the service and the date. Three hours later text the client a thank-you with our
    Google review link. If they haven't been back in 4 weeks, text them to rebook. If it's been
    over 4 months, give me a task to call them. Send me a monthly summary.
  expect_artifacts: [form, segment, workflow, message_template, report]
  forbid_terms: [discount]
