# Origamy playbook spec v1. Reference: /playbooks/spec-format/
spec_version: 1
id: retail.after-the-sale
title: After the sale
sector: retail
source:
  kind: synthesized
  notes: >-
    No shop has built this with us yet. Written from the base patterns: a staff form that records
    each sale and saves its date on the customer (as the church playbooks record a
    gift), a trigger filter on a form answer to pick delivery sales, and a daily run
    over a date-rule segment for restocking. WhatsApp templates follow the Meta approval rules.
vocabulary:
  person: customer
  people: customers
  group: team
summary: >-
  After you record a sale, delivery customers get a "did it arrive OK?" message two days later,
  and every customer gets a review or Instagram-tag request after five days. Regulars who haven't
  bought for a month get a "time to restock?" message with your catalogue link. You get a monthly
  summary. Works over WhatsApp or SMS.
when_to_use:
  - You sell on Instagram, WhatsApp, at a market stall or from a small shop, and many customers buy again.
  - Orders are paid by bank transfer or at the counter, and nobody follows up after the sale.
  - You want more reviews and more repeat orders without writing every message yourself.

requires:
  data_in:
    - how: staff_form
      what: "A Record a sale form that you or your seller fills after each sale, on a phone, matched to the customer by phone number. It doesn't ask for prices."
    - how: csv_import
      what: "Optional: import your regulars with last_purchase_at (an ISO date) and first_name, so the restock messages work from day one."
  channels:
    - channel: whatsapp
      for: All three messages, if you choose WhatsApp
      needs: A connected WhatsApp Business number, and each template approved by Meta (submit it from the template page). The templates must be approved before you switch the follow-ups on.
    - channel: sms
      for: All three messages, if you choose SMS
      needs: An SMS provider, or Origamy managed SMS after business verification (KYC).
  events:
    - onboarding.submitted
  traits:
    - last_purchase_at
    - last_category

parameters:
  - key: org_name
    ask: true
    question: What should messages call your shop or brand?
    label: Shop name
    type: string
    example: Northwind Boutique
  - key: categories
    ask: true
    question: What do you sell? (the categories you pick from when recording a sale)
    help: One per line. Short names.
    label: Product categories
    type: list
    default: [Clothing, Beauty, Accessories, Home]
  - key: reorder_days
    ask: true
    question: After how many days do regular customers usually need to restock?
    help: For example 30 for skincare or food, 90 for clothes.
    label: Restock after (days)
    type: number
    default: 30
  - key: review_url
    ask: true
    question: Where should happy customers leave a review or tag you? (your Instagram page, a Google review link)
    label: Review or tag link
    type: url
    example: https://www.instagram.com/yourshop
  - key: catalogue_url
    ask: true
    question: Which link should restock messages point to? (your WhatsApp catalogue or online shop)
    label: Catalogue link
    type: url
    example: https://wa.me/c/yourshop
  - key: use_whatsapp
    ask: true
    question: Send these messages on WhatsApp instead of SMS?
    help: WhatsApp needs a connected business number and Meta's approval of each message. SMS needs an SMS provider.
    label: Use WhatsApp
    type: boolean
    default: false
  - key: restock_until_days
    label: Stop asking about restocking this many days after the last purchase
    type: number
    default: 60
  - key: restock_cooldown_hours
    label: Hours before the same customer can get the restock message again
    type: hours
    default: 720
  - key: delivery_check_delay
    label: Delivery check after the sale
    type: duration
    default: 2d
  - key: review_delay
    label: Review request after the sale
    type: duration
    default: 5d
  - key: send_time
    label: Daily send time for the restock message
    type: time
    default: "10:00"
  - key: shop_contact
    label: Number or handle customers can message
    type: string
    default: "{{ workspace.phone }}"
  - key: report_team
    label: Team that gets the monthly summary
    type: team_name
    default: Owners
  - key: report_cron
    label: Monthly report (five-field cron, in the time zone below)
    type: cron
    default: "0 9 1 * *"
  - key: timezone
    label: Time zone
    type: timezone
    default: "{{ workspace.timezone }}"
  - key: default_country
    label: Default phone country on the form
    type: country_code
    default: "{{ workspace.country }}"
  - key: greeting_fallback
    label: Greeting when a first name is missing
    type: string
    default: there

timings:
  - "{{ params.delivery_check_delay }} after a delivery sale is recorded: a message asking whether it arrived OK. Pickup sales don't get it."
  - "{{ params.review_delay }} after any sale: a request for a review or an Instagram tag, at most once a month per customer."
  - "Every day at {{ params.send_time }} ({{ params.timezone }}): a restock message to customers whose last purchase was more than {{ params.reorder_days }} days ago and less than {{ params.restock_until_days }} days ago."
  - Monthly summary on the schedule {{ params.report_cron }}.

customer_must:
  - "After each sale, fill the Record a sale form: the customer's phone number, first name, what they bought (a category), the date and pickup or delivery. It's ten seconds on your phone. Record sales the same day, or the messages arrive late."
  - "If you chose WhatsApp: connect your WhatsApp Business number, then submit the three templates for Meta approval from the template page and wait for them to be approved. Meta can take a day or more. Give a real review link and catalogue link, because the WhatsApp messages need them."
  - "If you chose SMS: connect an SMS provider or finish business verification."
  - Only message customers who've agreed to hear from you. Mention it at the sale or in your order message, and respect anyone who says stop.
  - Add people to the {{ params.report_team }} team, publish the Record a sale form, switch the follow-ups and the report on one at a time, and test with your own phone and a test sale.

objects:
  - ref: team_report
    kind: team
    about: Gets the monthly summary.
    body: { name: "{{ params.report_team }}" }

  - ref: form_sale
    kind: form
    about: Staff form to record a sale. It saves the purchase date and category on the customer. It asks for no prices.
    body:
      name: Record a sale
      slug: record-a-sale
      description: For you and your sellers. One entry per sale.
      draft_state:
        version: 1
        meta: { layout: classic, submit_label: Record sale, success_message: Recorded. }
        pages:
          - id: p1
            title: Record a sale
            description: Use the customer's phone number each time so their orders stay together.
            fields:
              - { id: f_phone, key: phone, type: phone, label: Customer's phone (WhatsApp if possible), 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: true, maps_to: { mode: custom, key: first_name, trait: true } }
              - id: f_category
                key: category
                type: single_select
                label: What did they buy?
                required: true
                maps_to: { mode: custom, key: last_category, trait: true }
                options:
                  - $each: params.categories
                    $value: { value: "{{ item }}", label: "{{ item }}" }
              - { id: f_date, key: sale_date, type: date, label: Date of the sale, required: true, maps_to: { mode: custom, key: last_purchase_at, trait: true }, config: { default_today: true } }
              - id: f_delivery
                key: delivery_method
                type: multiple_choice
                label: Pickup or delivery?
                required: true
                maps_to: { mode: custom, key: delivery_method }
                options: [{ value: pickup, label: Pickup }, { value: delivery, label: Delivery }]
      verification_settings: { required: false }

  - ref: seg_restock
    kind: segment
    about: Last purchase more than the restock time ago, but not too long ago.
    body:
      name: Time to restock
      description: last_purchase_at more than the restock days ago and within the stop-asking window.
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: last_purchase_at, cond_operator: older_than, value: "{{ params.reorder_days }}d" }
          - { type: trait, trait_key: last_purchase_at, cond_operator: within_last, value: "{{ params.restock_until_days }}d" }
  - ref: seg_recent
    kind: segment
    about: Bought in the last 30 days (for the report).
    body:
      name: Bought in the last 30 days
      description: last_purchase_at within the last 30 days.
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: last_purchase_at, cond_operator: within_last, value: 30d }

  - ref: tmpl_delivery_sms
    kind: template
    when: { param: use_whatsapp, equals: false }
    about: '"Did it arrive OK?" by SMS.'
    body:
      name: Delivery check (SMS)
      channel: sms
      category: After the sale
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hi {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, this is {{ params.org_name }}. Did your order arrive OK? If anything is wrong, call or message us on {{ params.shop_contact }} and we'll sort it out. Reply STOP to opt out."
  - ref: tmpl_delivery_wa
    kind: template
    when: { param: use_whatsapp, equals: true }
    about: '"Did it arrive OK?" on WhatsApp. Needs Meta approval.'
    body:
      name: Delivery check (WhatsApp)
      channel: whatsapp
      category: After the sale
      style: text
      status: active
      subject: ""
      provider_template_id: after_sale_delivery_check
      metadata: { whatsapp: { category: UTILITY, language: en } }
      variables: { "1": "{{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}", "2": "{{ params.org_name }}" }
      body: "Hi {{1}}, this is {{2}}. Did your order arrive OK? If anything is wrong, just reply to this message and we will sort it out."
  - ref: tmpl_review_sms
    kind: template
    when: { param: use_whatsapp, equals: false }
    about: Review or tag request by SMS.
    body:
      name: Review request (SMS)
      channel: sms
      category: After the sale
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hi {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, thank you for shopping with {{ params.org_name }}! If you love your order, a quick review or a tag helps us a lot{% if \"{{ params.review_url }}\" != \"\" %}: {{ params.review_url }}{% endif %}. Reply STOP to opt out."
  - ref: tmpl_review_wa
    kind: template
    when: { param: use_whatsapp, equals: true }
    about: Review or tag request on WhatsApp. Needs Meta approval.
    body:
      name: Review request (WhatsApp)
      channel: whatsapp
      category: After the sale
      style: text
      status: active
      subject: ""
      provider_template_id: after_sale_review_ask
      metadata: { whatsapp: { category: MARKETING, language: en } }
      variables: { "1": "{{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}", "2": "{{ params.org_name }}", "3": "{{ params.review_url }}" }
      body: "Hi {{1}}, thank you for shopping with {{2}}! If you love your order, a quick review or a tag helps us a lot: {{3}} Thank you!"
  - ref: tmpl_restock_sms
    kind: template
    when: { param: use_whatsapp, equals: false }
    about: '"Time to restock?" by SMS.'
    body:
      name: Restock reminder (SMS)
      channel: sms
      category: After the sale
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hi {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, it's been a while since your last order from {{ params.org_name }}. Time to restock?{% if \"{{ params.catalogue_url }}\" != \"\" %} See what's new: {{ params.catalogue_url }}{% endif %} Reply STOP to opt out."
  - ref: tmpl_restock_wa
    kind: template
    when: { param: use_whatsapp, equals: true }
    about: '"Time to restock?" on WhatsApp. Needs Meta approval.'
    body:
      name: Restock reminder (WhatsApp)
      channel: whatsapp
      category: After the sale
      style: text
      status: active
      subject: ""
      provider_template_id: after_sale_restock
      metadata: { whatsapp: { category: MARKETING, language: en } }
      variables: { "1": "{{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}", "2": "{{ params.org_name }}", "3": "{{ params.catalogue_url }}" }
      body: "Hi {{1}}, it has been a little while since your last order from {{2}}. Time to restock? See what is new in our catalogue: {{3}} Reply here if you would like help choosing."

  - ref: q_sales
    kind: saved_question
    about: How many sales were recorded in the period.
    body:
      name: "Report: sales recorded"
      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_sale.id }}" }
        aggregations: [{ fn: count, alias: sales }]
  - ref: q_buyers
    kind: saved_question
    about: How many different customers bought in the period.
    body:
      name: "Report: customers who bought"
      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_sale.id }}" }
        aggregations: [{ fn: distinct, field: canonical_user_id, alias: people }]
  - ref: q_by_category
    kind: saved_question
    about: Sales recorded per category.
    body:
      name: "Report: sales by category"
      data_plane_id: default
      viz_type: table
      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_sale.id }}" }
        aggregations: [{ fn: count, alias: sales }]
        breakouts: [{ field: properties.category }]
        limit: 50
  - ref: report_monthly
    kind: report
    about: Sales recorded, customers who bought, sales by category and who is due to restock.
    body:
      name: "Monthly: sales summary"
      description: Sales recorded last month, customers who bought (the difference is repeat sales), sales by category, and who is due to restock.
      schedule_cron: "{{ params.report_cron }}"
      timezone: "{{ params.timezone }}"
      enabled: false
      spec:
        period: previous_month
        compare: previous_period
        week_start: monday
        sections:
          - { id: sales, kind: saved_question, title: Sales recorded, viz: number, params: { question_id: "{{ ref.q_sales.id }}" } }
          - { id: buyers, kind: saved_question, title: Customers who bought, viz: number, params: { question_id: "{{ ref.q_buyers.id }}" } }
          - { id: categories, kind: saved_question, title: Sales by category, viz: table, params: { question_id: "{{ ref.q_by_category.id }}" } }
          - id: sizes
            kind: segment_sizes
            title: Customers now
            params:
              segment_ids: ["{{ ref.seg_recent.id }}", "{{ ref.seg_restock.id }}"]
      recipients: [{ type: team, id: "{{ ref.team_report.id }}" }]
      channel_policy: { mode: email_only }

  - ref: wf_delivery
    kind: workflow
    about: Two days after a delivery sale, asks whether the order arrived OK. Pickup sales are skipped.
    body:
      name: "After the sale: did it arrive?"
      description: Two days after a delivery sale is recorded, a message asking whether the order arrived OK.
      enabled: false
      trigger_type: event
      trigger_config:
        event_name: onboarding.submitted
        form_id: "{{ ref.form_sale.id }}"
        property_filters: [{ property: delivery_method, cond_operator: eq, value: delivery }]
        properties: { $origamy_property_filters: true }
      steps:
        - { id: s1, type: delay, config: { duration: "{{ params.delivery_check_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_delivery_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_delivery_wa.id }}" } } } }
      re_entry_policy: every_time
      re_entry_cooldown_hours: 0
      goal: {}
      exit_conditions: []
  - ref: wf_review
    kind: workflow
    about: Five days after a sale, asks for a review or an Instagram tag; at most once a month per customer.
    body:
      name: "After the sale: review or tag"
      description: Five days after a sale is recorded, a request for a review or an Instagram tag.
      enabled: false
      trigger_type: event
      trigger_config: { event_name: onboarding.submitted, form_id: "{{ ref.form_sale.id }}" }
      steps:
        - { id: s1, type: delay, config: { duration: "{{ params.review_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_review_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_review_wa.id }}" } } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 720
      goal: {}
      exit_conditions: []
  - ref: wf_restock
    kind: workflow
    about: Daily run over "time to restock"; one message per cooldown.
    body:
      name: "After the sale: time to restock?"
      description: Every day, a restock message to customers whose last purchase was a while ago, with your catalogue link.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_restock.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_restock_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_restock_wa.id }}" } } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: "{{ params.restock_cooldown_hours }}"
      goal: {}
      exit_conditions: []

eval:
  name: 94_retail_after_the_sale
  mode: task
  vertical: ""
  prompt: >-
    I sell clothes and skincare on Instagram and WhatsApp. I'll record every sale on a form. Two
    days after a delivery sale, message the customer on WhatsApp to ask if it arrived OK. Five
    days after any sale, ask them to leave a review or tag us on Instagram. Customers who haven't
    bought for 30 days should get a message asking if it's time to restock, with my catalogue
    link. Send me a monthly summary of sales.
  expect_artifacts: [form, segment, workflow, message_template, report]
  forbid_terms: [discount, cart, checkout, abandoned]
