# Origamy playbook spec v1. Reference: /playbooks/spec-format/
spec_version: 1
id: logistics.merchant-onboarding
title: Merchant onboarding and first shipment
sector: logistics
source:
  kind: synthesized
  notes: >-
    No logistics platform has built this with us yet. Written from the first-contact follow-up
    (a new lead gets a call task due the next day), the trial-to-paid nudge (a reminder tied to
    the one action that shows the product works, stopping when it happens) and the win-back
    playbook (a call for accounts that go quiet). B2B shippers usually decide in the first week,
    and a call from an account manager is the main thing that gets them to send a first parcel.
derived_from: base.first-contact-follow-up
vocabulary:
  person: merchant
  people: merchants
  group: account managers
summary: >-
  A new merchant gets a welcome and a call task for an account manager due within a day. If they
  haven't created a shipment three days later they get a nudge. A merchant who has shipped before
  but not for 30 days gets an account manager check-in. A monthly report goes to sales.
when_to_use:
  - You run a logistics platform or courier for businesses (online sellers, shops, shippers), and many sign up but never send a parcel.
  - New merchants should get a call from an account manager quickly, and quiet ones should be noticed before they leave.
  - Your platform can send an event when a merchant creates a shipment.

requires:
  data_in:
    - how: public_form
      what: "The Merchant sign-up form this playbook creates: business name, contact, phone, email, monthly volume and city. Put its link on your website, WhatsApp and sales material."
    - how: app_events
      what: "Your platform sends an event for each shipment a merchant creates: that is how the nudge and the check-in know who is shipping. If merchants sign up in your app, it can also send a sign-up event instead of using the form."
  channels:
    - channel: sms
      for: The welcome and the first-shipment nudge
      needs: An SMS provider, or Origamy managed SMS after business verification (KYC).
    - channel: email
      for: The welcome with how to get started
      needs: A verified sending domain for {{ params.email_from }}.
  events:
    - "{{ params.signup_event }}"
    - "{{ params.shipment_event }}"
    - onboarding.submitted
  traits:
    - business_name
    - monthly_volume
    - city
    - account_manager_call

parameters:
  - key: org_name
    ask: true
    question: What should messages call your logistics company?
    label: Company name
    type: string
    example: Northwind Logistics
  - key: signup_via
    ask: true
    question: How do merchants sign up? Choose form if they use the sign-up form, or app_events if your platform sends a sign-up event.
    label: Sign-up source
    type: enum
    options: [form, app_events]
    default: form
  - key: sales_team
    ask: true
    question: Which team calls new merchants and quiet ones? (account managers, or one person)
    label: Account managers
    type: team_name
    default: Account managers
  - key: app_url
    ask: true
    question: Where do merchants create shipments? (your app or merchant portal)
    label: Merchant app link
    type: url
    example: https://merchant.yourcompany.example
  - key: shipment_event
    ask: true
    question: Which event means a merchant created a shipment?
    label: Shipment event
    type: event_name
    default: Shipment Created
  - key: signup_event
    label: Merchant sign-up event
    type: event_name
    default: Merchant Signed Up
  - key: volume_ranges
    label: Monthly shipment ranges on the sign-up form
    type: list
    default: [Under 50 parcels a month, 50 to 200 parcels a month, Over 200 parcels a month]
  - key: sales_phone
    label: Number merchants call to reach sales
    type: string
    default: "{{ workspace.phone }}"
  - key: call_due_hours
    label: Hours an account manager has to call a new merchant
    type: hours
    default: 24
  - key: nudge_delay
    label: How long to wait before the first-shipment nudge
    type: duration
    default: 3d
  - key: checkin_due_hours
    label: Hours an account manager has to check in on a quiet merchant
    type: hours
    default: 48
  - key: email_from
    label: Sender address
    type: email
    default: "{{ workspace.default_sender }}"
  - key: default_country
    label: Default phone country on the form
    type: country_code
    default: "{{ workspace.country }}"
  - key: timezone
    label: Time zone for the report
    type: timezone
    default: "{{ workspace.timezone }}"
  - key: report_cron
    label: Monthly sales report
    type: cron
    default: "0 8 1 * *"
  - key: greeting_fallback
    label: Greeting when a first name is missing
    type: string
    default: there

timings:
  - "A new merchant gets the welcome SMS and email straight away, and a call task for {{ params.sales_team }} due in {{ params.call_due_hours }} hours."
  - "{{ params.nudge_delay }} after sign-up, a nudge by SMS if no {{ params.shipment_event }} has happened. It stops as soon as one does."
  - "A merchant who has shipped in the last 90 days but not in the last 30 gets a check-in task, due in {{ params.checkin_due_hours }} hours."
  - "Monthly sales report: {{ params.report_cron }} in {{ params.timezone }}."

customer_must:
  - "Send {{ params.shipment_event }} from your platform for every shipment a merchant creates, with the merchant's phone number as a trait. Without it the nudge goes to everyone and the check-in never fires."
  - "If merchants sign up in your app, send {{ params.signup_event }} with business_name, monthly_volume and city as traits. If they use the form, publish it."
  - Add people to the {{ params.sales_team }} team. A task assigned to an empty team reaches nobody.
  - Use the same phone number for the merchant on the form and in your platform, so Origamy matches them as one person.
  - Tell merchants when they sign up that you will contact them about their account.
  - Verify the sending domain, connect an SMS provider or finish business verification, switch the follow-ups and report on, and test with your own phone.

objects:
  - ref: team_sales
    kind: team
    about: Calls new and quiet merchants; gets the monthly report.
    body: { name: "{{ params.sales_team }}" }

  - ref: form_signup
    kind: form
    about: The merchant sign-up form. Saves the business name, volume and city on the merchant.
    when: { param: signup_via, equals: form }
    body:
      name: Merchant sign-up
      slug: merchant-signup
      description: Tell us about your business and an account manager will call you.
      draft_state:
        version: 1
        meta: { layout: classic, submit_label: Sign up, success_message: "Thank you! An account manager will call you soon to set up your account." }
        pages:
          - id: p1
            title: About your business
            description: We'll use this to set up your account.
            fields:
              - { id: f_biz, key: business_name, type: short_text, label: Business name, required: true, maps_to: { mode: custom, key: business_name, trait: true } }
              - { 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_last, key: last_name, type: short_text, label: Your last name, required: false, maps_to: { mode: custom, key: last_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_volume
                key: monthly_volume
                type: single_select
                label: How many parcels do you send in a month?
                required: true
                maps_to: { mode: custom, key: monthly_volume, trait: true }
                options:
                  $each: params.volume_ranges
                  $value: { value: "{{ item }}", label: "{{ item }}" }
              - { id: f_city, key: city, type: short_text, label: Which city do you ship from?, required: true, maps_to: { mode: custom, key: city, trait: true } }
      verification_settings: { required: false }

  - ref: seg_no_shipment
    kind: segment
    about: No shipment created in the last 14 days.
    body:
      name: No shipment yet
      description: "No {{ params.shipment_event }} in the last 14 days."
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: event, event_name: "{{ params.shipment_event }}", cond_operator: has_not_done, time_window: 14d }
  - ref: seg_active
    kind: segment
    about: Created a shipment in the last 30 days.
    body:
      name: Active merchants
      description: "{{ params.shipment_event }} in the last 30 days."
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: event, event_name: "{{ params.shipment_event }}", cond_operator: has_done, time_window: 30d }
  - ref: seg_quiet
    kind: segment
    about: Shipped in the last 90 days but not in the last 30.
    body:
      name: Quiet merchants
      description: "{{ params.shipment_event }} in the last 90 days but not in the last 30."
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: event, event_name: "{{ params.shipment_event }}", cond_operator: has_done, time_window: 90d }
          - { type: event, event_name: "{{ params.shipment_event }}", cond_operator: has_not_done, time_window: 30d }

  - ref: tmpl_welcome_sms
    kind: template
    about: Welcome SMS.
    body:
      name: Welcome merchant (SMS)
      channel: sms
      category: Merchants
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "{{ params.org_name }}: welcome, {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}! An account manager will call you soon to get you set up. Questions before then? Call {{ params.sales_phone }}. Reply STOP to opt out."
  - ref: tmpl_welcome_email
    kind: template
    about: Welcome email with how to get started.
    body:
      name: Welcome merchant (email)
      channel: email
      category: Merchants
      style: html
      status: active
      subject: "Welcome to {{ params.org_name }}, {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}"
      metadata: { email: { from: "{{ params.email_from }}", preview_text: "Here's how to send your first parcel." } }
      variables: {}
      body: >-
        <p>Hi {{ traits.first_name | default: "{{ params.greeting_fallback }}" }},</p>
        <p>Thank you for choosing {{ params.org_name }}{% if traits.business_name %} for {{ traits.business_name }}{% endif %}.
        An account manager will call you soon to set up your account and answer any questions.</p>
        <p>When you're ready, you can create your first shipment here:</p>
        <p><a href="{{ params.app_url }}">Create a shipment</a></p>
        <p>Need us sooner? Call {{ params.sales_phone }}.</p>
        <p style="font-size:12px;color:#6b6b80"><a href="{{ unsubscribe_url }}">Unsubscribe</a></p>
  - ref: tmpl_nudge
    kind: template
    about: First-shipment nudge by SMS.
    body:
      name: Send your first shipment (SMS)
      channel: sms
      category: Merchants
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "{{ params.org_name }}: ready to send your first parcel, {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}? Create a shipment in a few minutes: {{ params.app_url }} Need help? Call {{ params.sales_phone }}. Reply STOP to opt out."

  - ref: q_new_merchants
    kind: saved_question
    about: New merchants in the report's period.
    body:
      name: "Report: new merchants"
      data_plane_id: default
      viz_type: number
      viz_settings: {}
      visibility: workspace
      query_ast:
        table: events
        filters:
          operator: and
          conditions:
            - { when: { param: signup_via, equals: form }, field: event_name, op: eq, value: onboarding.submitted }
            - { when: { param: signup_via, equals: form }, field: properties.form_id, op: eq, value: "{{ ref.form_signup.id }}" }
            - { when: { param: signup_via, equals: app_events }, field: event_name, op: eq, value: "{{ params.signup_event }}" }
        aggregations: [{ fn: distinct, field: canonical_user_id, alias: people }]
  - ref: q_shipping
    kind: saved_question
    about: Merchants who created a shipment in the report's period.
    body:
      name: "Report: merchants shipping"
      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: "{{ params.shipment_event }}" }] }
        aggregations: [{ fn: distinct, field: canonical_user_id, alias: people }]
  - ref: report_monthly
    kind: report
    about: Last month's new merchants and merchants shipping, how many are active or quiet now, and the state of the calls, sent to sales.
    body:
      name: "Monthly: merchants"
      description: Last month's new and shipping merchants, and who has gone quiet.
      schedule_cron: "{{ params.report_cron }}"
      timezone: "{{ params.timezone }}"
      enabled: false
      spec:
        period: previous_month
        compare: previous_period
        week_start: monday
        sections:
          - { id: new, kind: saved_question, title: New merchants, viz: number, params: { question_id: "{{ ref.q_new_merchants.id }}" } }
          - { id: shipping, kind: saved_question, title: Merchants shipping, viz: number, params: { question_id: "{{ ref.q_shipping.id }}" } }
          - id: merchants
            kind: segment_sizes
            title: Active and quiet merchants now
            params: { segment_ids: ["{{ ref.seg_active.id }}", "{{ ref.seg_quiet.id }}"] }
          - { id: tasks, kind: tasks_summary, title: Account manager calls, params: {} }
      recipients: [{ type: team, id: "{{ ref.team_sales.id }}" }]
      channel_policy: { mode: email_only }

  - ref: wf_new_form
    kind: workflow
    about: A new sign-up on the form gets a welcome and an account manager call task due the next day.
    when: { param: signup_via, equals: form }
    body:
      name: "Merchant 1: new merchant (form)"
      description: Welcome SMS and email, and a call task for the account managers; the outcome is saved.
      enabled: false
      trigger_type: event
      trigger_config: { event_name: onboarding.submitted, form_id: "{{ ref.form_signup.id }}" }
      steps:
        - { id: s1, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_welcome_sms.id }}" } } } }
        - { id: s2, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: email, template_id: "{{ ref.tmpl_welcome_email.id }}" } } } }
        - id: s3
          type: human_task
          config:
            human_task:
              title: "New merchant: {{ traits.business_name | default: traits.first_name }}"
              instructions: |-
                {{ traits.first_name }} {{ traits.last_name }} ({{ traits.phone }}) signed up for {{ traits.business_name | default: "a new business" }} in {{ traits.city | default: "an unknown city" }}. Expected volume: {{ traits.monthly_volume | default: "not given" }}.

                Call to understand what they ship, where to and how often, set up their account and help them create a first shipment. Choose an outcome and add a short note.
              assignee_refs: [{ type: team, id: "{{ ref.team_sales.id }}" }]
              strategy: round_robin
              completion_policy: first_response_wins
              outcome_options:
                - { id: onboarded, label: Account set up, intent: positive }
                - { id: follow_up, label: Needs a follow-up, intent: neutral }
                - { 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
        - { id: s4, type: action, config: { action_type: set_trait, traits: { account_manager_call: "{{ properties.task_outcome | default: \"unknown\" }}", account_manager_note: "{{ properties.task_note }}" } } }
      re_entry_policy: once
      re_entry_cooldown_hours: 24
      goal: { event_name: "{{ params.shipment_event }}", attribution_window_hours: 720 }
      exit_conditions: []
  - ref: wf_new_app
    kind: workflow
    about: A merchant who signs up in your app gets a welcome and an account manager call task due the next day.
    when: { param: signup_via, equals: app_events }
    body:
      name: "Merchant 1: new merchant (app)"
      description: Welcome SMS and email, and a call task for the account managers; the outcome is saved.
      enabled: false
      trigger_type: event
      trigger_config: { event_name: "{{ params.signup_event }}" }
      steps:
        - { id: s1, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_welcome_sms.id }}" } } } }
        - { id: s2, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: email, template_id: "{{ ref.tmpl_welcome_email.id }}" } } } }
        - id: s3
          type: human_task
          config:
            human_task:
              title: "New merchant: {{ traits.business_name | default: traits.first_name }}"
              instructions: |-
                {{ traits.first_name }} {{ traits.last_name }} ({{ traits.phone }}) signed up for {{ traits.business_name | default: "a new business" }} in {{ traits.city | default: "an unknown city" }}. Expected volume: {{ traits.monthly_volume | default: "not given" }}.

                Call to understand what they ship, where to and how often, set up their account and help them create a first shipment. Choose an outcome and add a short note.
              assignee_refs: [{ type: team, id: "{{ ref.team_sales.id }}" }]
              strategy: round_robin
              completion_policy: first_response_wins
              outcome_options:
                - { id: onboarded, label: Account set up, intent: positive }
                - { id: follow_up, label: Needs a follow-up, intent: neutral }
                - { 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
        - { id: s4, type: action, config: { action_type: set_trait, traits: { account_manager_call: "{{ properties.task_outcome | default: \"unknown\" }}", account_manager_note: "{{ properties.task_note }}" } } }
      re_entry_policy: once
      re_entry_cooldown_hours: 24
      goal: { event_name: "{{ params.shipment_event }}", attribution_window_hours: 720 }
      exit_conditions: []
  - ref: wf_nudge_form
    kind: workflow
    about: Three days after a form sign-up, a nudge to send a first parcel, if they haven't.
    when: { param: signup_via, equals: form }
    body:
      name: "Merchant 2: first-shipment nudge (form)"
      description: A nudge for merchants who haven't created a shipment; stops when they do.
      enabled: false
      trigger_type: event
      trigger_config: { event_name: onboarding.submitted, form_id: "{{ ref.form_signup.id }}" }
      steps:
        - { id: s1, type: delay, config: { duration: "{{ params.nudge_delay }}" } }
        - { id: s2, type: filter, config: { condition: { type: segment, cond_operator: member_of, value: "{{ ref.seg_no_shipment.id }}" } } }
        - { id: s3, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_nudge.id }}" } } } }
      re_entry_policy: once
      re_entry_cooldown_hours: 24
      goal: { event_name: "{{ params.shipment_event }}", attribution_window_hours: 720 }
      exit_conditions: [{ event_name: "{{ params.shipment_event }}" }]
  - ref: wf_nudge_app
    kind: workflow
    about: Three days after an app sign-up, a nudge to send a first parcel, if they haven't.
    when: { param: signup_via, equals: app_events }
    body:
      name: "Merchant 2: first-shipment nudge (app)"
      description: A nudge for merchants who haven't created a shipment; stops when they do.
      enabled: false
      trigger_type: event
      trigger_config: { event_name: "{{ params.signup_event }}" }
      steps:
        - { id: s1, type: delay, config: { duration: "{{ params.nudge_delay }}" } }
        - { id: s2, type: filter, config: { condition: { type: segment, cond_operator: member_of, value: "{{ ref.seg_no_shipment.id }}" } } }
        - { id: s3, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_nudge.id }}" } } } }
      re_entry_policy: once
      re_entry_cooldown_hours: 24
      goal: { event_name: "{{ params.shipment_event }}", attribution_window_hours: 720 }
      exit_conditions: [{ event_name: "{{ params.shipment_event }}" }]
  - ref: wf_quiet
    kind: workflow
    about: A merchant who stops shipping for 30 days gets an account manager check-in; stops when they ship again.
    body:
      name: "Merchant 3: quiet merchant check-in"
      description: A check-in call task when a merchant who was shipping has sent nothing for 30 days.
      enabled: false
      trigger_type: segment_enter
      trigger_config: { segment_id: "{{ ref.seg_quiet.id }}" }
      steps:
        - id: s1
          type: human_task
          config:
            human_task:
              title: "Check in: {{ traits.business_name | default: traits.first_name }}"
              instructions: |-
                {{ traits.first_name }} {{ traits.last_name }} ({{ traits.phone }}) from {{ traits.business_name | default: "a merchant" }} was shipping with us but hasn't created a shipment in 30 days.

                Call to ask how business is, whether anything went wrong with a delivery or the price, and what would bring their parcels back. Choose an outcome and add a short note.
              assignee_refs: [{ type: team, id: "{{ ref.team_sales.id }}" }]
              strategy: round_robin
              completion_policy: first_response_wins
              outcome_options:
                - { id: will_ship, label: Will ship again, intent: positive }
                - { id: issue_raised, label: Raised an issue, intent: neutral }
                - { id: churned, label: Moved elsewhere, intent: negative }
                - { id: not_reached, label: Could not reach, intent: negative }
              due_hours: "{{ params.checkin_due_hours }}"
              wait: true
              wait_timeout_hours: 168
              notify: immediate
        - { id: s2, type: action, config: { action_type: set_trait, traits: { quiet_check_in: "{{ properties.task_outcome | default: \"unknown\" }}", quiet_check_in_note: "{{ properties.task_note }}" } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 2160
      goal: { event_name: "{{ params.shipment_event }}", attribution_window_hours: 720 }
      exit_conditions: [{ event_name: "{{ params.shipment_event }}" }]

eval:
  name: 110_logistics_merchant_onboarding
  mode: task
  vertical: ""
  prompt: >-
    We're a delivery platform for online sellers. Make a merchant sign-up form with business
    name, contact name, phone, email, monthly parcel volume and city. When someone signs up,
    text and email a welcome and give our account managers a task to call within 24 hours. If
    they haven't created a shipment after three days, text them a nudge, and stop when they
    do. If a merchant who was shipping hasn't sent anything for 30 days, give an account
    manager a task to check in. Our platform sends a Shipment Created event. Send our sales
    team a monthly report of new and active merchants.
  expect_artifacts: [form, segment, workflow, message_template, report]
  forbid_terms: []
