# Origamy playbook spec v1. Reference: /playbooks/spec-format/
spec_version: 1
id: restaurants.catering-enquiries
title: Catering and event enquiries
sector: restaurants
source:
  kind: synthesized
  notes: >-
    No restaurant has built this with us yet. Built from the base first-contact call-task
    pattern (a call task with an escalation, the outcome saved on the person) plus the daily
    segment-and-schedule pattern on the event date. The chase after a quote and the staff form
    that marks an event booked are new.
derived_from: base.first-contact-follow-up
vocabulary:
  person: customer
  people: customers
  group: team
summary: >-
  Catering and event enquiries come through a form and reach your events team as a call task due
  within two hours, with an escalation to the owner. A quote that has not turned into a booking
  after three days gets a chase call. Booked events get a reminder and a final-details call, and a
  thank-you afterwards.
when_to_use:
  - You take catering, party-tray, wedding or corporate orders on top of your normal service.
  - Enquiries arrive on Instagram, WhatsApp and the phone, and some get answered too late.
  - You quote, then forget to follow up, and the customer books someone else.

requires:
  data_in:
    - how: public_form
      what: "The Catering enquiry form this playbook creates. Put its link in your Instagram bio, on your menu and in your WhatsApp business profile."
    - how: staff_form
      what: "The Catering booked form this playbook creates. Your team fills it when a customer confirms, matched by phone."
  channels:
    - channel: sms
      for: The enquiry acknowledgement, the event reminder and the thank-you
      needs: An SMS provider, or Origamy managed SMS after business verification (KYC).
  events:
    - onboarding.submitted
  traits:
    - event_date
    - catering_status
    - catering_call

parameters:
  - key: org_name
    ask: true
    question: What should messages call your restaurant or catering business?
    label: Business name
    type: string
    example: Northwind Kitchen
  - key: events_team
    ask: true
    question: Which team calls catering enquiries? (can be just you)
    label: Events team
    type: team_name
    default: Events
  - key: owner_team
    ask: true
    question: Who should hear if an enquiry isn't called back in time?
    label: Escalation team
    type: team_name
    default: Owner
  - key: review_url
    ask: true
    question: Where should customers leave a review after their event? (leave empty to just say thank you)
    label: Review link
    type: url
    default: ""
  - key: budget_ranges
    ask: true
    question: What budget ranges should the form offer? Write them in your own currency.
    label: Budget ranges
    type: list
    default: [Under 100k, 100k to 300k, 300k to 600k, Over 600k, Not sure yet]
  - key: event_types
    label: Kinds of event on the form
    type: list
    default: [Wedding, Birthday, Corporate event, Family gathering, Other]
  - key: call_due_hours
    label: Hours to make the first call
    type: hours
    default: 2
  - key: escalate_after_hours
    label: Hours after which an uncalled enquiry escalates
    type: hours
    default: 4
  - key: chase_after
    label: How long after a quote to chase
    type: duration
    default: 3d
  - key: caterer_phone
    label: Number customers call
    type: string
    default: "{{ workspace.phone }}"
  - key: send_time
    label: Daily send time
    type: time
    default: "09:00"
  - key: greeting_fallback
    label: Greeting when a first name is missing
    type: string
    default: there
  - key: default_country
    label: Default phone country on the forms
    type: country_code
    default: "{{ workspace.country }}"
  - key: timezone
    label: Time zone
    type: timezone
    default: "{{ workspace.timezone }}"

timings:
  - The customer gets an acknowledgement text straight away. A call task for {{ params.events_team }} is created at once, due in {{ params.call_due_hours }} hours, and escalates to {{ params.owner_team }} after {{ params.escalate_after_hours }} hours.
  - "{{ params.chase_after }} after a call that ended in a quote, if the event isn't booked, a chase task is created for {{ params.events_team }}."
  - Every day at {{ params.send_time }}, booked events within the next 3 days get a reminder text and a final-details call task, and events from 1 to 3 days ago get a thank-you text.

customer_must:
  - Add your catering staff to the {{ params.events_team }} team and the owner to {{ params.owner_team }}. A task for an empty team reaches nobody.
  - Publish the Catering enquiry form and add its link to your Instagram bio, menu and WhatsApp profile. Publish the Catering booked form and bookmark it for your team.
  - "When a customer confirms (and pays the deposit if you take one), a team member fills the Catering booked form. Until then the customer is a quote, and after three days the chase task is created."
  - Connect an SMS provider or finish business verification, check the two segments, then switch the follow-ups on.
  - Keep quotes and menus out of the texts. The playbook only reminds and thanks.

objects:
  - ref: team_events
    kind: team
    about: Calls catering enquiries and chases quotes. Can be one person.
    body: { name: "{{ params.events_team }}" }
  - ref: team_owner
    kind: team
    about: Hears about enquiries that weren't called in time.
    body: { name: "{{ params.owner_team }}" }

  - ref: form_enquiry
    kind: form
    about: The catering enquiry form. A hidden field marks the customer as an enquiry.
    body:
      name: Catering enquiry
      slug: catering-enquiry
      description: Tell us about your event and we'll call you.
      draft_state:
        version: 1
        meta: { layout: classic, submit_label: Send enquiry, success_message: "Thank you! Someone from {{ params.org_name }} will call you soon." }
        pages:
          - id: p1
            title: Tell us about your event
            description: A few details help us give you a good quote.
            fields:
              - { id: f_first, key: first_name, type: short_text, label: Your 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_email, key: email, type: email, label: Email address (optional), required: false, maps_to: { mode: mapped, slot: profile.email } }
              - id: f_type
                key: event_type
                type: single_select
                label: What kind of event is it?
                required: true
                maps_to: { mode: custom, key: event_type }
                options:
                  - $each: params.event_types
                    $value: { value: "{{ item }}", label: "{{ item }}" }
              - { id: f_date, key: event_date, type: date, label: Date of the event, required: true, maps_to: { mode: custom, key: event_date, trait: true } }
              - { id: f_guests, key: guests, type: number, label: How many guests?, required: true, maps_to: { mode: custom, key: guests } }
              - id: f_budget
                key: budget
                type: single_select
                label: Roughly what is your budget?
                required: false
                maps_to: { mode: custom, key: budget }
                options:
                  - $each: params.budget_ranges
                    $value: { value: "{{ item }}", label: "{{ item }}" }
              - { id: f_location, key: location, type: short_text, label: Where is the event? (area or venue), required: true, maps_to: { mode: custom, key: location } }
              - { id: f_notes, key: notes, type: long_text, label: Anything else we should know? (menu ideas, allergies), required: false, maps_to: { mode: custom, key: notes } }
              - { id: f_status, key: status, type: hidden, label: "", required: false, default: enquiry, maps_to: { mode: custom, key: catering_status, trait: true } }
      verification_settings: { required: false }
  - ref: form_booked
    kind: form
    about: Staff form to mark an event as booked. Matched to the customer by phone.
    body:
      name: Catering booked
      slug: catering-booked
      description: Staff only. Fill this when a customer confirms their event.
      draft_state:
        version: 1
        meta: { layout: classic, submit_label: Mark as booked, success_message: Saved. }
        pages:
          - id: p1
            title: Catering booked
            description: Use the phone number the customer gave on the enquiry.
            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_date, key: event_date, type: date, label: Confirmed event date, required: true, maps_to: { mode: custom, key: event_date, trait: true } }
              - id: f_deposit
                key: deposit_received
                type: multiple_choice
                label: Deposit received?
                required: true
                maps_to: { mode: custom, key: deposit_received, trait: true }
                options: [{ value: "yes", label: "Yes" }, { value: "no", label: "No" }]
              - { id: f_status, key: status, type: hidden, label: "", required: false, default: booked, maps_to: { mode: custom, key: catering_status, trait: true } }
      verification_settings: { required: false }

  - ref: seg_event_soon
    kind: segment
    about: Booked events in the next three days.
    body:
      name: Booked event soon
      description: catering_status is booked and the event is within 3 days.
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: catering_status, cond_operator: eq, value: booked }
          - { type: trait, trait_key: event_date, cond_operator: within_next, value: 3d }
  - ref: seg_event_done
    kind: segment
    about: Booked events that ended yesterday or in the last few days.
    body:
      name: Booked event just done
      description: catering_status is booked and the event date was between 1 and 3 days ago.
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: catering_status, cond_operator: eq, value: booked }
          - { type: trait, trait_key: event_date, cond_operator: older_than, value: 1d }
          - { type: trait, trait_key: event_date, cond_operator: within_last, value: 3d }

  - ref: tmpl_ack
    kind: template
    about: Acknowledgement right after an enquiry.
    body:
      name: Catering enquiry received (SMS)
      channel: sms
      category: Enquiries
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hi {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, thank you for your catering enquiry with {{ params.org_name }}. We'll call you shortly. If it's urgent, call {{ params.caterer_phone }}. Reply STOP to opt out."
  - ref: tmpl_reminder
    kind: template
    about: Reminder a few days before a booked event.
    body:
      name: Catering event coming up (SMS)
      channel: sms
      category: Enquiries
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hi {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, your event with {{ params.org_name }} is on {{ traits.event_date | date: \"%a %-d %b\" }}. We'll call you to go over the final details. Need to change anything? Call {{ params.caterer_phone }}. Reply STOP to opt out."
  - ref: tmpl_thanks
    kind: template
    about: Thank-you after the event, with a review link if you gave one.
    body:
      name: Catering thank you (SMS)
      channel: sms
      category: Feedback
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hi {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, thank you for choosing {{ params.org_name }} for your event. We hope it went well.{% if \"{{ params.review_url }}\" != \"\" %} If you have a minute, a review helps us a lot: {{ params.review_url }}{% endif %} Reply STOP to opt out."

  - ref: wf_enquiry
    kind: workflow
    about: Acknowledges the enquiry, creates the first call task, and chases a quote that hasn't turned into a booking.
    body:
      name: "Catering enquiry: call and chase"
      description: Acknowledges the enquiry, creates a call task with an escalation, saves the outcome, and chases an unbooked quote.
      enabled: false
      trigger_type: event
      trigger_config: { event_name: onboarding.submitted, form_id: "{{ ref.form_enquiry.id }}" }
      steps:
        - { id: s1, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_ack.id }}" } } } }
        - id: s2
          type: human_task
          config:
            human_task:
              title: "Catering enquiry: {{ properties.event_type | default: \"event\" }} for {{ properties.guests | default: \"?\" }}"
              instructions: |-
                {{ traits.first_name | default: "A customer" }} ({{ traits.phone }}) asked about catering.
                Event: {{ properties.event_type }} on {{ properties.event_date }}, {{ properties.guests }} guests, at {{ properties.location }}.
                Budget: {{ properties.budget | default: "not given" }}
                Notes: {{ properties.notes | default: "none" }}

                Call them, ask what they need and give a quote. Choose Quoted if you gave them a price, Not a fit if you can't do it, or Could not reach. Add a short note.
              assignee_refs: [{ type: team, id: "{{ ref.team_events.id }}" }]
              strategy: round_robin
              completion_policy: first_response_wins
              outcome_options:
                - { id: quoted, label: Quoted, intent: positive }
                - { id: not_a_fit, label: Not a fit, 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
              escalation_ladder:
                - { after_hours: "{{ params.escalate_after_hours }}", to: [{ type: team, id: "{{ ref.team_owner.id }}" }] }
        - { id: s3, type: action, config: { action_type: set_trait, traits: { catering_call: "{{ properties.task_outcome | default: \"unknown\" }}", catering_note: "{{ properties.task_note }}" } } }
        - { id: s4, type: delay, config: { duration: "{{ params.chase_after }}" } }
        - { id: s5, type: filter, config: { condition: { type: trait, trait_key: catering_call, cond_operator: eq, value: quoted } } }
        - { id: s6, type: filter, config: { condition: { type: trait, trait_key: catering_status, cond_operator: neq, value: booked } } }
        - id: s7
          type: human_task
          config:
            human_task:
              title: "Chase the quote: {{ traits.first_name | default: \"a customer\" }}"
              instructions: |-
                {{ traits.first_name | default: "A customer" }} ({{ traits.phone }}) was quoted for an event on {{ traits.event_date | date: "%-d %b" }} and hasn't confirmed.
                Our note from the first call: {{ traits.catering_note | default: "none" }}

                Call to ask if they have questions and if they'd like to go ahead. If they confirm, fill the Catering booked form.
              assignee_refs: [{ type: team, id: "{{ ref.team_events.id }}" }]
              strategy: round_robin
              completion_policy: first_response_wins
              outcome_options:
                - { id: booking, label: Going ahead, intent: positive }
                - { id: thinking, label: Still thinking, intent: neutral }
                - { id: lost, label: Went elsewhere, intent: negative }
              due_hours: 24
              wait: true
              wait_timeout_hours: 168
              notify: immediate
        - { id: s8, type: action, config: { action_type: set_trait, traits: { catering_chase: "{{ properties.task_outcome | default: \"unknown\" }}" } } }
      re_entry_policy: every_time
      re_entry_cooldown_hours: 0
      goal: {}
      exit_conditions: []
  - ref: wf_reminder
    kind: workflow
    about: Daily run over "booked event soon"; one reminder and a final-details call per event.
    body:
      name: "Catering: event reminder and final call"
      description: Every day, a reminder text and a final-details call task for booked events in the next 3 days.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_event_soon.id }}", rrule: FREQ=DAILY, starts_at: "{{ workspace.tomorrow }}T{{ params.send_time }}", timezone: "{{ params.timezone }}" }
      steps:
        - { id: s1, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_reminder.id }}" } } } }
        - id: s2
          type: human_task
          config:
            human_task:
              title: "Final details call: {{ traits.first_name | default: \"a customer\" }}"
              instructions: |-
                {{ traits.first_name | default: "A customer" }} ({{ traits.phone }}) has an event on {{ traits.event_date | date: "%a %-d %b" }}. Deposit received: {{ traits.deposit_received | default: "not recorded" }}.
                Call to confirm the menu, numbers, delivery or setup time and the address. Note any changes.
              assignee_refs: [{ type: team, id: "{{ ref.team_events.id }}" }]
              strategy: round_robin
              completion_policy: first_response_wins
              outcome_options:
                - { id: confirmed, label: Details confirmed, intent: positive }
                - { id: changes, label: Changes needed, intent: neutral }
                - { id: not_reached, label: Could not reach, intent: negative }
              due_hours: 24
              wait: true
              wait_timeout_hours: 72
              notify: immediate
        - { id: s3, type: action, config: { action_type: set_trait, traits: { final_details_call: "{{ properties.task_outcome | default: \"unknown\" }}" } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 336
      goal: {}
      exit_conditions: []
  - ref: wf_thanks
    kind: workflow
    about: Daily run over "booked event just done"; one thank-you per event.
    body:
      name: "Catering: thank you after the event"
      description: Every day, a thank-you text with the review link for events from 1 to 3 days ago.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_event_done.id }}", rrule: FREQ=DAILY, starts_at: "{{ workspace.tomorrow }}T{{ params.send_time }}", timezone: "{{ params.timezone }}" }
      steps:
        - { id: s1, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_thanks.id }}" } } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 720
      goal: {}
      exit_conditions: []

eval:
  name: 80_restaurants_catering_enquiries
  mode: task
  vertical: ""
  prompt: >-
    We do catering for weddings and parties. Make an enquiry form with the event date, number of
    guests, budget and location. When one comes in, give our events team a task to call within 2
    hours, and escalate to me after 4 hours. If we quoted and they haven't booked after 3 days,
    create a task to chase. A second form lets staff mark an event as booked. Text booked
    customers 3 days before the event, and after it thank them with our review link.
  expect_artifacts: [form, segment, workflow, message_template]
  forbid_terms: [discount]
