# Origamy playbook spec v1. Reference: /playbooks/spec-format/
spec_version: 1
id: base.payment-reminder
title: Renewal or payment reminder
sector: base
source:
  kind: synthesized
  notes: >-
    No customer has run a renewal reminder with us yet. Written from the base patterns: a
    due-date trait read with date operators, a daily schedule over "due soon" and "overdue"
    segments, and a call task when a payment is late. The stage-trait and stable-messageId
    advice comes from a real admissions build.
derived_from: null
vocabulary:
  person: person
  people: people
  group: team
summary: >-
  Reminds people before a renewal or payment is due, on the day, and after it's missed, and
  gives your team a call task when it's overdue. Runs once a day over a due-date your system
  keeps on each profile.
when_to_use:
  - People renew a plan, membership or subscription, or pay an invoice or instalment, on a known date.
  - Your system can keep the next due date on each profile as a trait.

requires:
  data_in:
    - how: sheet_sync
      what: "The ledger you already keep in Google Sheets or Excel, synced every hour as People: phone or email, next due date, paid or not."
    - how: app_events
      what: "Or your billing system updates the two traits."
  channels:
    - channel: email
      for: The reminders
      needs: A verified sending domain for {{ params.email_from }}.
    - channel: sms
      for: The due-day and overdue reminders
      needs: An SMS provider, or Origamy managed SMS after business verification (KYC).
  events: []
  traits:
    - "{{ params.due_trait }}"
    - "{{ params.status_trait }}"

parameters:
  - key: org_name
    ask: true
    question: What should messages call your organisation?
    label: Organisation name
    type: string
    example: Northwind
  - key: what_is_due
    ask: true
    question: What are you reminding people about? (in a few words, as messages will say it)
    label: What is due
    type: string
    default: your renewal
  - key: due_trait
    ask: true
    question: Which trait holds the next due date?
    help: A date or date-time trait your system updates after each payment.
    label: Due-date trait
    type: trait_key
    default: next_payment_due
  - key: pay_url
    ask: true
    question: Where do people pay or renew?
    label: Payment link
    type: url
    example: https://yourcompany.example/billing
  - key: collections_team
    ask: true
    question: Which team calls when a payment is overdue?
    label: Overdue team
    type: team_name
    default: Billing
  - key: email_from
    label: Sender address
    type: email
    default: "{{ workspace.default_sender }}"
  - key: status_trait
    label: Trait that says whether the current period is paid
    type: trait_key
    default: payment_status
  - key: remind_before
    label: First reminder before the due date
    type: duration
    default: 7d
  - key: send_time
    label: Daily send time
    type: time
    default: "09:00"
  - key: timezone
    label: Time zone for the daily run
    type: timezone
    default: "{{ workspace.timezone }}"
  - key: greeting_fallback
    label: Greeting when a first name is missing
    type: string
    default: there

timings:
  - Runs every day at {{ params.send_time }} in {{ params.timezone }}.
  - An email when the due date is within {{ params.remind_before }}, an SMS on the due day, and an SMS plus a call task for {{ params.collections_team }} once it's overdue (due in 48 hours).
  - Each reminder reaches a person at most once per period (re-entry cooldown).

customer_must:
  - "Fill {{ params.status_trait }} for everyone (paid or due). A blank status never matches, so that person gets no reminder."
  - "Keep {{ params.due_trait }} up to date on each profile (an ISO date like 2026-12-01), and set {{ params.status_trait }} to paid when the current period is paid, so nobody who has paid is reminded."
  - Add people to the {{ params.collections_team }} team.
  - Verify the sending domain; for SMS, connect a provider or finish business verification.
  - Check the three segments' sizes, then switch the follow-ups on.
  - Keep reminders factual and polite. Don't threaten, and follow your local rules for collections.

objects:
  - ref: team_collections
    kind: team
    about: Calls people whose payment is overdue.
    body: { name: "{{ params.collections_team }}" }
  - ref: seg_due_soon
    kind: segment
    about: Due within the reminder window and not yet paid.
    body:
      name: Payment due soon
      description: "{{ params.due_trait }} within the next {{ params.remind_before }}, not paid."
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: "{{ params.due_trait }}", cond_operator: within_next, value: "{{ params.remind_before }}" }
          - { type: trait, trait_key: "{{ params.status_trait }}", cond_operator: neq, value: paid }
  - ref: seg_due_today
    kind: segment
    about: Due today and not yet paid.
    body:
      name: Payment due today
      description: "{{ params.due_trait }} is today (a date-only value counts from midnight), not paid."
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: "{{ params.due_trait }}", cond_operator: within_last, value: 1d }
          - { type: trait, trait_key: "{{ params.status_trait }}", cond_operator: neq, value: paid }
  - ref: seg_overdue
    kind: segment
    about: Past the due date in the last 30 days and not paid.
    body:
      name: Payment overdue
      description: "{{ params.due_trait }} in the last 30 days, not paid."
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: "{{ params.due_trait }}", cond_operator: older_than, value: 1d }
          - { type: trait, trait_key: "{{ params.due_trait }}", cond_operator: within_last, value: 30d }
          - { type: trait, trait_key: "{{ params.status_trait }}", cond_operator: neq, value: paid }
  - ref: tmpl_soon_email
    kind: template
    about: Reminder before the due date.
    body:
      name: Payment due soon (email)
      channel: email
      category: Billing
      style: html
      status: active
      subject: "A reminder about {{ params.what_is_due }}"
      metadata: { email: { from: "{{ params.email_from }}", preview_text: "" } }
      variables: {}
      body: >-
        <p>Hi {{ traits.first_name | default: "{{ params.greeting_fallback }}" }},</p>
        <p>This is a friendly reminder that {{ params.what_is_due }} with {{ params.org_name }}
        is coming up soon.</p>
        <p><a href="{{ params.pay_url }}">Pay or renew now</a></p>
        <p>Already sorted? Thank you, you can ignore this email.</p>
        <p style="font-size:12px;color:#6b6b80"><a href="{{ unsubscribe_url }}">Unsubscribe</a></p>
  - ref: tmpl_today_sms
    kind: template
    about: SMS on the due day.
    body:
      name: Payment due today (SMS)
      channel: sms
      category: Billing
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hi {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, {{ params.what_is_due }} with {{ params.org_name }} is due today. Pay here: {{ params.pay_url }} Reply STOP to opt out."
  - ref: tmpl_overdue_sms
    kind: template
    about: SMS once overdue.
    body:
      name: Payment overdue (SMS)
      channel: sms
      category: Billing
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hi {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, we haven't received {{ params.what_is_due }} yet. If you need help, reply or call us. Pay here: {{ params.pay_url }} Reply STOP to opt out."
  - ref: wf_soon
    kind: workflow
    about: 'Daily run over "due soon": one email per period.'
    body:
      name: "Payment reminder 1: due soon"
      description: Every day, an email to people whose payment 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, config: { action_type: send_message, channel_config: { mode: single, single: { channel: email, template_id: "{{ ref.tmpl_soon_email.id }}" } } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 336
      goal: {}
      exit_conditions: []
  - ref: wf_today
    kind: workflow
    about: 'Daily run over "due today": one SMS.'
    body:
      name: "Payment reminder 2: due today"
      description: Every day, an SMS to people whose payment 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, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_today_sms.id }}" } } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 48
      goal: {}
      exit_conditions: []
  - ref: wf_overdue
    kind: workflow
    about: 'Daily run over "overdue": an SMS and a call task for the overdue team.'
    body:
      name: "Payment reminder 3: overdue"
      description: Every day, an SMS and a call task for people whose payment is overdue.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_overdue.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_overdue_sms.id }}" } } } }
        - { id: s2, type: delay, config: { duration: 2d } }
        - { id: s3, type: filter, config: { condition: { type: trait, trait_key: "{{ params.status_trait }}", cond_operator: neq, value: paid } } }
        - id: s4
          type: human_task
          config:
            human_task:
              title: "Overdue: {{ traits.first_name }} {{ traits.last_name }}"
              instructions: |-
                {{ traits.first_name }} {{ traits.last_name }} ({{ traits.phone }}) is past their due date and hasn't paid after an SMS.
                Call to check everything is OK and help them pay. Note what was agreed.
              assignee_refs: [{ type: team, id: "{{ ref.team_collections.id }}" }]
              strategy: round_robin
              completion_policy: first_response_wins
              outcome_options:
                - { id: paying, label: Will pay, intent: positive }
                - { id: plan, label: Needs a plan, intent: neutral }
                - { id: not_reached, label: Could not reach, intent: negative }
              due_hours: 48
              wait: true
              wait_timeout_hours: 168
              notify: immediate
        - { id: s5, type: action, config: { action_type: set_trait, traits: { overdue_call: "{{ properties.task_outcome | default: \"unknown\" }}", overdue_note: "{{ properties.task_note }}" } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 720
      goal: {}
      exit_conditions: []

eval:
  name: 65_base_payment_reminder
  mode: task
  vertical: ""
  prompt: >-
    Remind members a week before their renewal date by email, text them on the day, and if
    they still haven't paid, text again and give our billing team a task to call them. The
    renewal date is in the next_payment_due trait.
  expect_artifacts: [segment, workflow, message_template]
  forbid_terms: []
