# Origamy playbook spec v1. Reference: /playbooks/spec-format/
spec_version: 1
id: professional-services.enquiry-to-consultation
title: Enquiry to consultation
sector: professional-services
source:
  kind: synthesized
  notes: >-
    No firm has built this with us yet. Written from the base patterns: the call-task and
    escalation shape of a real visitor follow-up build, the booking-form reminder pattern,
    and a decision form that sets a status trait. Messages never state case,
    tax or fee details.
derived_from: base.first-contact-follow-up
vocabulary:
  person: client
  people: clients
  group: team
summary: >-
  Gives every enquiry a fast call from your intake team, with a link to book a consultation.
  Confirms and reminds each consultation, asks a partner to send the proposal the day after, and
  chases once if the client hasn't signed after five days.
when_to_use:
  - Lawyers, accountants, consultants, agencies or architects who win work from enquiries that arrive by form, WhatsApp or the website.
  - A new enquiry should be called within hours, and nobody should forget to send the proposal.
  - You want to see who was called, who booked, who signed and who went quiet.

requires:
  data_in:
    - how: public_form
      what: "The Enquiry form this playbook creates. Put its link on your website, in your Instagram or LinkedIn bio and in your WhatsApp replies."
    - how: booking_form
      what: "The Book a consultation form it creates (a date and time slot tied to your opening hours). Each booking saves its time on the client."
    - how: staff_form
      what: "A short Engagement form your team fills when a client signs, declines or is still deciding, matched by phone."
  channels:
    - channel: sms
      for: The booking link, the reminder
      needs: An SMS provider, or Origamy managed SMS after business verification (KYC).
    - channel: email
      for: The booking confirmation and what to prepare
      needs: A verified sending domain for {{ params.email_from }}.
  events:
    - onboarding.submitted
    - booking.created
    - booking.cancelled
  traits:
    - consultation_at
    - engagement_status
    - enquiry_call
    - proposal_outcome
    - chase_outcome

parameters:
  - key: org_name
    ask: true
    question: What should messages call your firm?
    label: Firm name
    type: string
    example: Northwind Advisory
  - key: services
    ask: true
    question: What services do you offer? (people pick one on the enquiry form)
    help: One per line. Short names, as clients would say them.
    label: Services
    type: list
    default: [Tax advice, Bookkeeping, Company registration, Something else]
  - key: intake_team
    ask: true
    question: Which team calls new enquiries? (it can be just you)
    label: Intake team
    type: team_name
    default: Intake
  - key: partner_team
    ask: true
    question: Which team sends proposals and hears about calls nobody made in time? (it can be just you)
    label: Partner team
    type: team_name
    default: Partners
  - key: prep_text
    ask: true
    question: What should clients bring or prepare for the consultation?
    label: What to prepare
    type: text
    example: your ID, your company registration number if you have one, and any letters you want us to look at
  - key: email_from
    label: Sender address
    type: email
    default: "{{ workspace.default_sender }}"
  - key: firm_phone
    label: Firm phone in messages
    type: string
    default: "{{ workspace.phone }}"
  - key: intake_due_hours
    label: Hours the intake team has to call
    type: hours
    default: 4
  - key: escalate_after_hours
    label: Hours after which an uncalled enquiry goes to the partners
    type: hours
    default: 24
  - key: proposal_due_hours
    label: Hours to send the proposal after the consultation
    type: hours
    default: 48
  - key: reminder_hours
    label: Consultation reminder, hours before
    type: hours
    default: 24
  - key: default_country
    label: Default phone country on the forms
    type: country_code
    default: "{{ workspace.country }}"
  - key: greeting_fallback
    label: Greeting when a first name is missing
    type: string
    default: there

timings:
  - Booking link by SMS straight after the enquiry. The call task is created at the same time, due in {{ params.intake_due_hours }} hours. If it isn't picked up after {{ params.escalate_after_hours }} hours it goes to {{ params.partner_team }}.
  - A booking is confirmed by email straight away, with what to prepare.
  - The reminder runs every hour and goes to anyone whose consultation is less than {{ params.reminder_hours }} hours away, once per consultation.
  - The proposal task is created the day after the consultation, due in {{ params.proposal_due_hours }} hours.
  - Five days after the consultation, if the client hasn't signed or declined, one chase task for {{ params.partner_team }}.

customer_must:
  - "Open the Book a consultation form in the form builder and choose the consultation service (its length and your opening hours) on the date and time question. A booking form can't be published until it has a service."
  - Add people to the {{ params.intake_team }} and {{ params.partner_team }} teams. A task assigned to an empty team reaches nobody.
  - "When a client signs or says no, fill the Engagement form (phone and decision). It stops the five-day chase for that client. Bookmark it on the partners' phones."
  - Publish the Enquiry, Book a consultation and Engagement forms, verify the sending domain, connect an SMS provider or finish business verification, then switch the follow-ups on one at a time and test with your own phone.
  - Keep messages general. Don't put case details, tax figures or fees in the forms' answers that messages use, and check your professional body's rules on client confidentiality.

objects:
  - ref: team_intake
    kind: team
    about: Calls new enquiries.
    body: { name: "{{ params.intake_team }}" }
  - ref: team_partners
    kind: team
    about: Sends proposals, chases, and hears about enquiries nobody called in time.
    body: { name: "{{ params.partner_team }}" }

  - ref: form_enquiry
    kind: form
    about: The enquiry form. A hidden field marks every enquirer as not yet a client.
    body:
      name: Enquiry
      slug: enquiry
      description: Tell us what you need 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: How can we help?
            description: A few details so we can call you back.
            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_last, key: last_name, type: short_text, label: Last name, required: true, maps_to: { mode: custom, key: last_name, trait: true } }
              - { id: f_company, key: company_name, type: short_text, label: Company or organisation (if any), required: false, maps_to: { mode: custom, key: company_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, required: true, maps_to: { mode: mapped, slot: profile.email } }
              - id: f_service
                key: service_needed
                type: single_select
                label: What do you need help with?
                required: true
                maps_to: { mode: custom, key: service_needed }
                options:
                  - $each: params.services
                    $value: { value: "{{ item }}", label: "{{ item }}" }
              - { id: f_description, key: enquiry_description, type: long_text, label: Tell us a little more (no need for private details), required: false, maps_to: { mode: custom, key: enquiry_description } }
              - id: f_urgency
                key: urgency
                type: single_select
                label: How soon do you need help?
                required: false
                maps_to: { mode: custom, key: urgency }
                options: [{ value: this_week, label: This week }, { value: this_month, label: This month }, { value: exploring, label: Just exploring }]
              - { id: f_status, key: stage, type: hidden, label: "", required: false, default: enquiry, maps_to: { mode: custom, key: engagement_status, trait: true } }
      verification_settings: { required: false }
  - ref: form_booking
    kind: form
    about: The consultation booking form. Each booking sends booking.created with the slot time.
    body:
      name: Book a consultation
      slug: book-a-consultation
      description: Pick a time for your consultation.
      draft_state:
        version: 1
        meta: { layout: classic, submit_label: Book consultation, success_message: "Booked! We'll email you a confirmation." }
        pages:
          - id: p1
            title: Pick a time
            description: Choose a day and time that suits you.
            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_email, key: email, type: email, label: Email address, required: true, maps_to: { mode: mapped, slot: profile.email } }
              - { id: f_slot, key: consultation_slot, type: date_slot, label: When would you like to meet?, required: true, maps_to: { mode: mapped, slot: booking.slot } }
      verification_settings: { required: false }
  - ref: form_engagement
    kind: form
    about: Partner-only form to record whether a client signed, declined or is still deciding. Matched by phone.
    body:
      name: Engagement
      slug: engagement
      description: Partners only. One entry when a client signs, declines or is still deciding.
      draft_state:
        version: 1
        meta: { layout: classic, submit_label: Save, success_message: Saved. }
        pages:
          - id: p1
            title: Engagement decision
            description: Use the phone number the client gave 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_decision
                key: engagement_decision
                type: multiple_choice
                label: Decision
                required: true
                maps_to: { mode: custom, key: engagement_status, trait: true }
                options: [{ value: signed, label: Signed }, { value: declined, label: Declined }, { value: pending, label: Still deciding }]
              - { id: f_note, key: engagement_note, type: long_text, label: Note (no private details), required: false, maps_to: { mode: custom, key: engagement_note } }
      verification_settings: { required: false }

  - ref: seg_soon
    kind: segment
    about: A consultation within the reminder window.
    body:
      name: Consultation soon
      description: "consultation_at within the next {{ params.reminder_hours }} hours."
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: consultation_at, cond_operator: within_next, value: "{{ params.reminder_hours }}h" }
  - ref: seg_yesterday
    kind: segment
    about: Had a consultation in the last day and hasn't declined.
    body:
      name: Consultation yesterday
      description: consultation_at within the last day, not declined.
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: consultation_at, cond_operator: within_last, value: 1d }
          - { type: trait, trait_key: engagement_status, cond_operator: neq, value: declined }
  - ref: seg_chase
    kind: segment
    about: Consultation 5 to 7 days ago and still neither signed nor declined.
    body:
      name: Consultation, no decision
      description: consultation_at between 5 and 7 days ago, engagement_status not signed or declined.
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: consultation_at, cond_operator: older_than, value: 5d }
          - { type: trait, trait_key: consultation_at, cond_operator: within_last, value: 7d }
          - { type: trait, trait_key: engagement_status, cond_operator: neq, value: signed }
          - { type: trait, trait_key: engagement_status, cond_operator: neq, value: declined }

  - ref: tmpl_enquiry_sms
    kind: template
    about: Thanks and the link to book a consultation.
    body:
      name: Enquiry received (SMS)
      channel: sms
      category: Enquiries
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hi {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, thanks for contacting {{ params.org_name }}. We'll call you soon. To pick a time for a consultation: {{ ref.form_booking.url }} Reply STOP to opt out."
  - ref: tmpl_booked_email
    kind: template
    about: Booking confirmation with what to prepare.
    body:
      name: Consultation booked (email)
      channel: email
      category: Appointments
      style: html
      status: active
      subject: "Your consultation with {{ params.org_name }}"
      metadata: { email: { from: "{{ params.email_from }}", preview_text: "" } }
      variables: {}
      body: >-
        <p>Hi {{ traits.first_name | default: "{{ params.greeting_fallback }}" }},</p>
        <p>Your consultation with {{ params.org_name }} is booked for
        {{ properties.slot_start_ts | date: "%A %-d %B, %H:%M" }}.</p>
        <p>Please bring or have ready: {{ params.prep_text }}</p>
        <p>To change the time, call {{ params.firm_phone }}.</p>
        <p>{{ params.org_name }}</p>
        <p style="font-size:12px;color:#6b6b80"><a href="{{ unsubscribe_url }}">Unsubscribe</a></p>
  - ref: tmpl_reminder_sms
    kind: template
    about: Reminder before the consultation.
    body:
      name: Consultation reminder (SMS)
      channel: sms
      category: Appointments
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "{{ params.org_name }}: a reminder of your consultation on {{ traits.consultation_at | date: \"%a %-d %b, %H:%M\" }}. To change it, call {{ params.firm_phone }}. Reply STOP to opt out."

  - ref: wf_enquiry_sms
    kind: workflow
    about: Thanks the enquirer and sends the booking link.
    body:
      name: "Enquiry: booking link"
      description: When the Enquiry form is submitted, an SMS with the link to book a consultation.
      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_enquiry_sms.id }}" } } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 24
      goal: {}
      exit_conditions: []
  - ref: wf_enquiry_call
    kind: workflow
    about: Intake call task with reminders and escalation to the partners; saves the outcome and note.
    body:
      name: "Enquiry: intake call"
      description: A call task for the intake team. Escalates to the partners if nobody picks it up. The outcome and note are saved on the client.
      enabled: false
      trigger_type: event
      trigger_config: { event_name: onboarding.submitted, form_id: "{{ ref.form_enquiry.id }}" }
      steps:
        - id: s1
          type: human_task
          config:
            human_task:
              title: "Enquiry: call {{ traits.first_name }} {{ traits.last_name }}"
              instructions: |-
                Please call {{ traits.first_name }} {{ traits.last_name }} on {{ traits.phone }}.
                Company: {{ properties.company_name | default: "none given" }}
                Needs help with: {{ properties.service_needed }}
                How soon: {{ properties.urgency | default: "not said" }}
                They wrote: {{ properties.enquiry_description | default: "nothing" }}

                Find out what they need, say how you work, and offer the consultation link we texted them. Choose an outcome and add a short note.
              assignee_refs: [{ type: team, id: "{{ ref.team_intake.id }}" }]
              strategy: round_robin
              completion_policy: first_response_wins
              outcome_options:
                - { id: booked, label: Consultation booked, intent: positive }
                - { id: not_a_fit, label: Not a fit, intent: neutral }
                - { id: not_reached, label: Could not reach, intent: negative }
              due_hours: "{{ params.intake_due_hours }}"
              wait: true
              wait_timeout_hours: 168
              notify: immediate
              escalation_ladder:
                - { after_hours: "{{ params.escalate_after_hours }}", to: [{ type: team, id: "{{ ref.team_partners.id }}" }] }
        - { id: s2, type: action, config: { action_type: set_trait, traits: { enquiry_call: "{{ properties.task_outcome | default: \"unknown\" }}", enquiry_note: "{{ properties.task_note }}" } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 24
      goal: {}
      exit_conditions: []
  - ref: wf_booked
    kind: workflow
    about: When a consultation is booked, save its time on the client and send the confirmation.
    body:
      name: "Consultation booked"
      description: Copies each booking's time onto the client and emails 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: { consultation_at: "{{ properties.slot_start_ts }}" } } }
        - { id: s2, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: email, template_id: "{{ ref.tmpl_booked_email.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, so no reminder goes out.
    body:
      name: "Consultation cancelled"
      description: Clears the saved consultation time when a booking is cancelled.
      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: { consultation_at: "" } } }
      re_entry_policy: every_time
      re_entry_cooldown_hours: 0
      goal: {}
      exit_conditions: []
  - ref: wf_reminder
    kind: workflow
    about: Hourly run over "consultation soon"; one reminder per consultation.
    body:
      name: "Consultation reminder"
      description: Every hour, an SMS reminder to clients whose consultation is close.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_soon.id }}", rrule: FREQ=HOURLY, starts_at: "{{ workspace.tomorrow }}T07:00", timezone: "{{ workspace.timezone }}" }
      steps:
        - { id: s1, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_reminder_sms.id }}" } } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 30
      goal: {}
      exit_conditions: []
  - ref: wf_proposal
    kind: workflow
    about: Daily run; a task for the partners to send the proposal the day after a consultation.
    body:
      name: "After the consultation: send the proposal"
      description: Every day, a proposal task for each client who had a consultation yesterday. The outcome is saved on the client.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_yesterday.id }}", rrule: FREQ=DAILY, starts_at: "{{ workspace.tomorrow }}T08:00", timezone: "{{ workspace.timezone }}" }
      steps:
        - id: s1
          type: human_task
          config:
            human_task:
              title: "Send the proposal: {{ traits.first_name }} {{ traits.last_name }}"
              instructions: |-
                {{ traits.first_name }} {{ traits.last_name }} ({{ traits.phone }}) had a consultation yesterday.
                Send the proposal and tell them when to expect your next message. Choose an outcome and add a short note.
              assignee_refs: [{ type: team, id: "{{ ref.team_partners.id }}" }]
              strategy: round_robin
              completion_policy: first_response_wins
              outcome_options:
                - { id: sent, label: Proposal sent, intent: positive }
                - { id: not_proceeding, label: Not proceeding, intent: neutral }
                - { id: needs_info, label: Waiting for information, intent: neutral }
              due_hours: "{{ params.proposal_due_hours }}"
              wait: true
              wait_timeout_hours: 168
              notify: immediate
        - { id: s2, type: action, config: { action_type: set_trait, traits: { proposal_outcome: "{{ properties.task_outcome | default: \"unknown\" }}" } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 336
      goal: {}
      exit_conditions: []
  - ref: wf_chase
    kind: workflow
    about: Daily run; one chase task for clients with no decision five days after the consultation.
    body:
      name: "After the consultation: chase"
      description: Every day, one chase task for clients who haven't signed or declined five days after the consultation.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_chase.id }}", rrule: FREQ=DAILY, starts_at: "{{ workspace.tomorrow }}T09:00", timezone: "{{ workspace.timezone }}" }
      steps:
        - id: s1
          type: human_task
          config:
            human_task:
              title: "Follow up: {{ traits.first_name }} {{ traits.last_name }}"
              instructions: |-
                {{ traits.first_name }} {{ traits.last_name }} ({{ traits.phone }}) had a consultation and hasn't told us their decision.
                Call or message to ask if they have questions about the proposal. Then fill the Engagement form so this stops.
              assignee_refs: [{ type: team, id: "{{ ref.team_partners.id }}" }]
              strategy: round_robin
              completion_policy: first_response_wins
              outcome_options:
                - { id: signed, label: Wants to go ahead, intent: positive }
                - { id: thinking, label: Still thinking, intent: neutral }
                - { id: declined, label: Not going ahead, intent: neutral }
                - { id: not_reached, label: Could not reach, intent: negative }
              due_hours: 48
              wait: true
              wait_timeout_hours: 168
              notify: immediate
        - { id: s2, type: action, config: { action_type: set_trait, traits: { chase_outcome: "{{ properties.task_outcome | default: \"unknown\" }}", chase_note: "{{ properties.task_note }}" } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 720
      goal: {}
      exit_conditions: []

eval:
  name: 89_professional_services_enquiry_to_consultation
  mode: task
  vertical: ""
  prompt: >-
    We're a small accounting firm. When someone fills in our enquiry form, text them a link to
    book a consultation and give our intake team a task to call them within 4 hours, escalating
    to the partners after a day. Remind people by SMS the day before their consultation. The day
    after it, give a partner a task to send the proposal. If they still haven't signed five days
    after the consultation, give the partner one more task to follow up.
  expect_artifacts: [form, segment, workflow, message_template]
  forbid_terms: [discount, case number, tax owed]
