# Origamy playbook spec v1. Reference: /playbooks/spec-format/
spec_version: 1
id: auto.service-reminders
title: Service reminders for garages and workshops
sector: auto
source:
  kind: synthesized
  notes: >-
    No garage or service centre has built this with us yet. Written from the base patterns: a
    short staff form matched by phone that records the job and the next service date, segments
    over that date, a daily schedule over each, and a call task for customers who are overdue.
    A new job logged by the staff form moves the date forward, which takes the customer out of
    the reminder segments by itself.
derived_from: null
vocabulary:
  person: customer
  people: customers
  group: service desk
summary: >-
  Staff log each finished job in a short form. The customer gets a thank-you and a review ask
  the next day, a reminder a week before their next service is due and on the day, and the
  service desk gets a task to call customers who are past due and haven't been back.
when_to_use:
  - You run a mechanic, service centre, tyre shop, body shop or car wash, and repeat customers are most of your income.
  - Customers forget when their next service is due, and you remember only when they call.
  - You don't have a booking system that texts people, but your staff can fill in a short form at the counter.

requires:
  data_in:
    - how: staff_form
      what: "The Job done form this playbook creates: staff fill it in at the counter or on a phone, matched to the customer by phone number."
    - how: sheet_sync
      what: "Or your customer sheet, synced every hour, with last_service_at and next_service_due columns."
  channels:
    - channel: sms
      for: The thank-you and the reminders
      needs: An SMS provider, or Origamy managed SMS after business verification (KYC).
    - channel: whatsapp
      for: The same three messages, if you answer yes to WhatsApp
      needs: A connected WhatsApp Business number, and the three templates approved by Meta.
  events:
    - onboarding.submitted
  traits:
    - last_service_at
    - next_service_due
    - last_service
    - service_call
    - service_call_note

parameters:
  - key: org_name
    ask: true
    question: What should messages call your workshop?
    label: Workshop name
    type: string
    example: Northwind Auto Care
  - key: shop_phone
    ask: true
    question: Which number should customers call or message to book?
    label: Workshop phone
    type: string
    default: "{{ workspace.phone }}"
  - key: service_types
    ask: true
    question: What jobs do you want to pick from on the form?
    help: One entry per job. Staff choose one when they log a job.
    label: Jobs
    type: list
    default: [Full service, Oil change, Brakes, Tyres, Car wash, Other]
  - key: booking_url
    ask: true
    question: Is there a link where customers can book? (leave empty to give your phone number only)
    label: Booking link
    type: url
    default: ""
  - key: use_whatsapp
    ask: true
    question: Should the messages go by WhatsApp instead of SMS?
    help: Needs a connected WhatsApp Business number and Meta-approved templates.
    label: Use WhatsApp
    type: boolean
    default: false
  - key: review_url
    label: Review link in the thank-you (leave empty to just say thank you)
    type: url
    default: ""
  - key: service_team
    label: Team that calls customers who are past due
    type: team_name
    default: Service desk
  - key: remind_before
    label: First reminder before the service is due
    type: duration
    default: 7d
  - key: missed_window
    label: How long after the due date to keep looking for customers who haven't returned
    type: duration
    default: 30d
  - key: call_due_hours
    label: Hours the desk has to make the call
    type: hours
    default: 72
  - key: send_time
    label: Daily send time
    type: time
    default: "09:00"
  - key: default_country
    label: Default phone country on the form
    type: country_code
    default: "{{ workspace.country }}"
  - key: timezone
    label: Time zone for the daily runs
    type: timezone
    default: "{{ workspace.timezone }}"
  - key: greeting_fallback
    label: Greeting when a first name is missing
    type: string
    default: there

timings:
  - The thank-you goes the day after the job is logged.
  - Runs every day at {{ params.send_time }} in {{ params.timezone }}. A reminder when the next service is due within {{ params.remind_before }}, another on the day it is due.
  - When the due date passed up to {{ params.missed_window }} ago and no newer job has been logged, the {{ params.service_team }} gets a call task, due in {{ params.call_due_hours }} hours. Each customer gets that task once every 45 days at most.

customer_must:
  - "Publish the Job done form and bookmark it on the counter computer or the staff's phones. Staff enter the customer's phone, the job, the date it was done and the date the next service is due (a good rule of thumb is six months or 5,000 km)."
  - "If you keep your records in a sheet instead, sync it with last_service_at and next_service_due columns (dates like 2026-12-01) and skip the form."
  - "If you chose WhatsApp: connect your WhatsApp Business number and submit the three templates (service_thank_you, service_due_soon and service_due_today) for Meta approval before switching the follow-ups on."
  - Add people to the {{ params.service_team }} team. If you run the shop alone, put yourself in it. A task assigned to an empty team reaches nobody.
  - Connect an SMS provider or finish business verification, switch the follow-ups on and test with your own phone and a job logged for today.
  - The plate number and mileage on the form stay on the job record. Don't put them in messages, and tell customers you keep their number only to remind them about their service.

objects:
  - ref: team_service
    kind: team
    about: Calls customers who are past due for a service and haven't been back.
    body: { name: "{{ params.service_team }}" }

  - ref: form_job
    kind: form
    about: Staff form to log a finished job, matched to the customer by phone. Saves the date done and the next service date.
    body:
      name: Job done
      slug: job-done
      description: Staff only. One entry per finished job.
      draft_state:
        version: 1
        meta: { layout: classic, submit_label: Save job, success_message: Saved. }
        pages:
          - id: p1
            title: Job done
            description: Enter the customer's phone and what you did.
            fields:
              - { id: f_phone, key: phone, type: phone, label: Customer'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: Customer's first name, required: false, maps_to: { mode: custom, key: first_name, trait: true } }
              - id: f_service
                key: service_type
                type: single_select
                label: What was done?
                required: true
                maps_to: { mode: custom, key: last_service, trait: true }
                options: { $each: params.service_types, $value: { value: "{{ item }}", label: "{{ item }}" } }
              - { id: f_done, key: done_on, type: date, label: Date done, required: true, maps_to: { mode: custom, key: last_service_at, trait: true }, config: { default_today: true } }
              - { id: f_next, key: next_due, type: date, label: Next service due on, required: true, maps_to: { mode: custom, key: next_service_due, trait: true } }
              - { id: f_mileage, key: mileage_note, type: short_text, label: "Mileage (optional, kept on the job only)", required: false, maps_to: { mode: custom, key: mileage_note } }
              - { id: f_plate, key: vehicle_plate, type: short_text, label: "Vehicle plate (optional, kept on the job only)", required: false, maps_to: { mode: custom, key: vehicle_plate } }
      verification_settings: { required: false }

  - ref: seg_due_soon
    kind: segment
    about: Next service due within the reminder window.
    body:
      name: Service due soon
      description: "next_service_due within the next {{ params.remind_before }}."
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: next_service_due, cond_operator: within_next, value: "{{ params.remind_before }}" }
  - ref: seg_due_today
    kind: segment
    about: Next service date is today (it started in the last day).
    body:
      name: Service due today
      description: next_service_due is today.
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: next_service_due, cond_operator: within_last, value: 1d }
  - ref: seg_missed
    kind: segment
    about: Service date passed more than a day ago, within the window, and no newer job moved it forward.
    body:
      name: Service overdue, not back yet
      description: "next_service_due more than a day ago but within {{ params.missed_window }}. A new job moves the date forward, which removes the customer."
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: next_service_due, cond_operator: older_than, value: 1d }
          - { type: trait, trait_key: next_service_due, cond_operator: within_last, value: "{{ params.missed_window }}" }

  - ref: tmpl_thanks_sms
    kind: template
    about: Thank-you the day after a job, with a review ask if you gave a link.
    body:
      name: Thanks for visiting (SMS)
      channel: sms
      category: After service
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hello {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, thank you for choosing {{ params.org_name }}. We hope the car is running well.{% if \"{{ params.review_url }}\" != \"\" %} A quick review helps us a lot: {{ params.review_url }}{% endif %} Reply STOP to opt out."
  - ref: tmpl_soon_sms
    kind: template
    about: Reminder before the next service is due.
    body:
      name: Service due soon (SMS)
      channel: sms
      category: Service reminders
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hello {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, {{ params.org_name }} here. Your car's next service is due on {{ traits.next_service_due | date: \"%-d %b\" }}. Book a time{% if \"{{ params.booking_url }}\" != \"\" %} here: {{ params.booking_url }} or{% endif %} call {{ params.shop_phone }}. Reply STOP to opt out."
  - ref: tmpl_today_sms
    kind: template
    about: Reminder on the day the service is due.
    body:
      name: Service due today (SMS)
      channel: sms
      category: Service reminders
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hello {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, {{ params.org_name }} here. Your car's service is due today. We can fit you in{% if \"{{ params.booking_url }}\" != \"\" %}: {{ params.booking_url }} or{% endif %} call {{ params.shop_phone }}. Reply STOP to opt out."
  - ref: tmpl_thanks_wa
    kind: template
    when: { param: use_whatsapp, equals: true }
    about: WhatsApp thank-you after a job (needs Meta approval).
    body:
      name: Thanks for visiting (WhatsApp)
      channel: whatsapp
      category: After service
      style: text
      status: active
      subject: ""
      provider_template_id: service_thank_you
      metadata: { whatsapp: { category: UTILITY, language: en } }
      variables:
        "1": "{{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}"
        "2": "{{ params.org_name }}"
      body: "Hello {{1}}, thank you for choosing {{2}}. We hope your car is running well. If you need anything, just reply to this message."
  - ref: tmpl_soon_wa
    kind: template
    when: { param: use_whatsapp, equals: true }
    about: WhatsApp reminder before the next service is due (needs Meta approval).
    body:
      name: Service due soon (WhatsApp)
      channel: whatsapp
      category: Service reminders
      style: text
      status: active
      subject: ""
      provider_template_id: service_due_soon
      metadata: { whatsapp: { category: UTILITY, language: en } }
      variables:
        "1": "{{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}"
        "2": "{{ params.org_name }}"
        "3": "{{ traits.next_service_due | date: \"%-d %b\" }}"
        "4": "{{ params.shop_phone }}"
      body: "Hello {{1}}, this is {{2}}. Your car's next service is due on {{3}}. To book a time, reply here or call {{4}}."
  - ref: tmpl_today_wa
    kind: template
    when: { param: use_whatsapp, equals: true }
    about: WhatsApp reminder on the due day (needs Meta approval).
    body:
      name: Service due today (WhatsApp)
      channel: whatsapp
      category: Service reminders
      style: text
      status: active
      subject: ""
      provider_template_id: service_due_today
      metadata: { whatsapp: { category: UTILITY, language: en } }
      variables:
        "1": "{{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}"
        "2": "{{ params.org_name }}"
        "3": "{{ params.shop_phone }}"
      body: "Hello {{1}}, this is {{2}}. Your car's service is due today and we can fit you in. Reply here or call {{3}} to book."

  - ref: wf_thanks
    kind: workflow
    about: The day after a job is logged, a thank-you (and review ask).
    body:
      name: "After the job: thank you"
      description: When a job is logged, a thank-you the next day.
      enabled: false
      trigger_type: event
      trigger_config: { event_name: onboarding.submitted, form_id: "{{ ref.form_job.id }}" }
      steps:
        - { id: s1, type: delay, config: { duration: 1d } }
        - { 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: 168
      goal: {}
      exit_conditions: []
  - ref: wf_soon
    kind: workflow
    about: 'Daily run over "service due soon": one reminder per service.'
    body:
      name: "Service reminder 1: due soon"
      description: Every day, a reminder to customers whose next service is due soon.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_due_soon.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_soon_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_soon_wa.id }}" } } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 336
      goal: {}
      exit_conditions: []
  - ref: wf_today
    kind: workflow
    about: 'Daily run over "service due today": one reminder.'
    body:
      name: "Service reminder 2: due today"
      description: Every day, a reminder to customers whose service is due today.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_due_today.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_today_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_today_wa.id }}" } } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 72
      goal: {}
      exit_conditions: []
  - ref: wf_missed
    kind: workflow
    about: 'Daily run over "service overdue, not back yet": a call task for the service desk; the outcome is saved.'
    body:
      name: "Service reminder 3: call when overdue"
      description: When a customer is past their service date and hasn't been back, a call task for the service desk.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_missed.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: "Service overdue: {{ traits.first_name | default: \"customer\" }} {{ traits.last_name }}"
              instructions: |-
                {{ traits.first_name | default: "This customer" }} ({{ traits.phone }}) was due for a service and hasn't been back.
                Their last job: {{ traits.last_service | default: "not recorded" }}.
                Call to ask if they would like to book, and offer a time. Note what they said.
              assignee_refs: [{ type: team, id: "{{ ref.team_service.id }}" }]
              strategy: round_robin
              completion_policy: first_response_wins
              outcome_options:
                - { id: booked, label: Booked, intent: positive }
                - { id: later, label: Will call later, intent: neutral }
                - { id: not_needed, label: Done elsewhere or sold the car, intent: neutral }
                - { id: not_reached, label: Could not reach, intent: negative }
              due_hours: "{{ params.call_due_hours }}"
              wait: true
              wait_timeout_hours: 168
              notify: immediate
        - { id: s2, type: action, config: { action_type: set_trait, traits: { service_call: "{{ properties.task_outcome | default: \"unknown\" }}", service_call_note: "{{ properties.task_note }}" } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 1080
      goal: {}
      exit_conditions: []

eval:
  name: 88_auto_service_reminders
  mode: task
  vertical: ""
  prompt: >-
    I run a garage. Give my staff a short form to log each job with the customer's phone, the
    job and the date the next service is due. Text the customer a thank-you the next day, remind
    them a week before their next service and on the day, and if they're a month past due and
    haven't been back, give my service desk a task to call. Keep plate numbers out of the
    messages.
  expect_artifacts: [form, segment, workflow, message_template]
  forbid_terms: [plate]
