# Origamy playbook spec v1. Reference: /playbooks/spec-format/
spec_version: 1
id: real-estate.rent-and-lease-reminders
title: Rent and lease reminders
sector: real-estate
source:
  kind: synthesized
  notes: >-
    No landlord or property manager has built this with us yet. Written from the base
    payment-reminder pattern (a due-date trait, a daily schedule over "due soon", "due today" and
    "overdue" segments, a call task when late), with a lease-end reminder added. The wording is
    neutral about whether rent is monthly or yearly.
derived_from: base.payment-reminder
vocabulary:
  person: tenant
  people: tenants
  group: property managers
summary: >-
  Reminds tenants before rent is due, on the day and when it is late, and gives your property
  manager a call task when rent is overdue. Also reminds tenants and your team when a lease is
  about to end, so renewals get talked about early. Runs once a day over the dates in your
  tenant sheet.
when_to_use:
  - You keep a tenant list in Google Sheets or Excel with each tenant's rent due date and whether it is paid.
  - Rent is paid monthly, quarterly or once a year, and you chase it by hand today.
  - You want to talk to tenants about renewing well before the lease ends.

requires:
  data_in:
    - how: sheet_sync
      what: "Your tenant sheet, synced every hour as People: phone, email, unit, rent due date, rent status (paid or due) and lease end date."
    - how: csv_import
      what: "Or a one-off CSV of the same columns to start with."
  channels:
    - channel: sms
      for: The rent reminders (or WhatsApp, if you switch it on)
      needs: An SMS provider, or Origamy managed SMS after business verification (KYC).
    - channel: whatsapp
      for: The rent reminders, if you answer yes to WhatsApp
      needs: A connected WhatsApp Business number, and each of the three templates approved by Meta.
    - channel: email
      for: The lease-end reminder
      needs: A verified sending domain for {{ params.email_from }}.
  events: []
  traits:
    - "{{ params.rent_due_trait }}"
    - "{{ params.rent_status_trait }}"
    - "{{ params.lease_end_trait }}"
    - unit
    - overdue_call
    - overdue_note
    - lease_conversation

parameters:
  - key: org_name
    ask: true
    question: What should messages call you? (your company or the building)
    label: Name in messages
    type: string
    example: Northwind Homes
  - key: pay_info
    ask: true
    question: How do tenants pay? (a few words, without account numbers)
    help: Say it the way you would say it out loud. The message never carries bank details.
    label: How to pay
    type: text
    example: by bank transfer to the account on your tenancy agreement
  - key: manager_team
    ask: true
    question: Which team calls tenants whose rent is late and talks about renewals? (one person is fine)
    label: Property managers
    type: team_name
    default: Property managers
  - key: manager_phone
    ask: true
    question: Which number should tenants call with questions?
    label: Manager phone
    type: string
    default: "{{ workspace.phone }}"
  - key: use_whatsapp
    ask: true
    question: Should the rent reminders go by WhatsApp instead of SMS?
    help: Needs a connected WhatsApp Business number and Meta-approved templates.
    label: Use WhatsApp
    type: boolean
    default: false
  - key: rent_due_trait
    label: Trait with the rent due date
    type: trait_key
    default: rent_due_date
  - key: rent_status_trait
    label: Trait that says if the current rent is paid (paid or due)
    type: trait_key
    default: rent_status
  - key: lease_end_trait
    label: Trait with the lease end date
    type: trait_key
    default: lease_end_date
  - key: remind_before
    label: First rent reminder before the due date
    type: duration
    default: 14d
  - key: overdue_window
    label: How long after the due date to keep chasing
    type: duration
    default: 30d
  - key: lease_notice
    label: How long before the lease ends to start the renewal talk
    type: duration
    default: 60d
  - 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: email_from
    label: Sender address
    type: email
    default: "{{ workspace.default_sender }}"
  - 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 }}.
  - A reminder when rent is due within {{ params.remind_before }}, another on the due day, and an SMS (or WhatsApp) plus a call task for {{ params.manager_team }} once it is late, for up to {{ params.overdue_window }}. The call task is created two days after the message if rent is still unpaid.
  - The lease-end email and renewal task start when the lease ends within {{ params.lease_notice }}. Each tenant gets it once per lease (a 90-day cooldown).

customer_must:
  - "Keep these columns in your tenant sheet: phone, email, unit, {{ params.rent_due_trait }} (a date like 2026-12-01), {{ params.rent_status_trait }} (exactly paid or due, never blank) and {{ params.lease_end_trait }}. Set the status to paid when rent is received and back to due when the next period starts, so nobody who has paid is reminded."
  - Add your property managers to the {{ params.manager_team }} team. If you manage everything yourself, put yourself in it. A task assigned to an empty team reaches nobody.
  - "If you chose WhatsApp: connect your WhatsApp Business number and submit the three rent templates (rent_due_soon_reminder, rent_due_today_reminder and rent_overdue_reminder) for Meta approval before switching the follow-ups on."
  - Verify the sending domain for the lease email. For SMS, connect a provider or finish business verification.
  - Check the segment sizes against your sheet, then switch the follow-ups on and test with your own phone and a fake tenant.
  - Keep reminders factual and polite. Messages never carry amounts, bank details or threats, and the tone should follow your local tenancy rules.

objects:
  - ref: team_managers
    kind: team
    about: Calls tenants with late rent and talks about lease renewals.
    body: { name: "{{ params.manager_team }}" }

  - ref: seg_rent_soon
    kind: segment
    about: Rent due within the reminder window and not paid.
    body:
      name: Rent due soon
      description: "{{ params.rent_due_trait }} within the next {{ params.remind_before }}, not paid."
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: "{{ params.rent_due_trait }}", cond_operator: within_next, value: "{{ params.remind_before }}" }
          - { type: trait, trait_key: "{{ params.rent_status_trait }}", cond_operator: neq, value: paid }
  - ref: seg_rent_today
    kind: segment
    about: Rent due today (the date started in the last day) and not paid.
    body:
      name: Rent due today
      description: "{{ params.rent_due_trait }} is today, not paid."
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: "{{ params.rent_due_trait }}", cond_operator: within_last, value: 1d }
          - { type: trait, trait_key: "{{ params.rent_status_trait }}", cond_operator: neq, value: paid }
  - ref: seg_rent_overdue
    kind: segment
    about: Past the due date by a day or more, within the chasing window, and not paid.
    body:
      name: Rent overdue
      description: "{{ params.rent_due_trait }} more than a day ago but within {{ params.overdue_window }}, not paid."
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: "{{ params.rent_due_trait }}", cond_operator: older_than, value: 1d }
          - { type: trait, trait_key: "{{ params.rent_due_trait }}", cond_operator: within_last, value: "{{ params.overdue_window }}" }
          - { type: trait, trait_key: "{{ params.rent_status_trait }}", cond_operator: neq, value: paid }
  - ref: seg_lease_ending
    kind: segment
    about: Lease ending within the notice period.
    body:
      name: Lease ending soon
      description: "{{ params.lease_end_trait }} within the next {{ params.lease_notice }}."
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: "{{ params.lease_end_trait }}", cond_operator: within_next, value: "{{ params.lease_notice }}" }

  - ref: tmpl_soon_sms
    kind: template
    about: SMS before the rent is due.
    body:
      name: Rent due soon (SMS)
      channel: sms
      category: Rent
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hello {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, a friendly reminder from {{ params.org_name }}: your rent is due on {{ traits.{{ params.rent_due_trait }} | date: \"%-d %b %Y\" }}. Pay {{ params.pay_info }}. Already paid? Thank you. Questions? Call {{ params.manager_phone }}. Reply STOP to opt out."
  - ref: tmpl_today_sms
    kind: template
    about: SMS on the due day.
    body:
      name: Rent due today (SMS)
      channel: sms
      category: Rent
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hello {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, {{ params.org_name }} here: your rent is due today. Pay {{ params.pay_info }}. Already paid? Thank you. Questions? Call {{ params.manager_phone }}. Reply STOP to opt out."
  - ref: tmpl_overdue_sms
    kind: template
    about: SMS once rent is late. Factual, with an offer to talk.
    body:
      name: Rent overdue (SMS)
      channel: sms
      category: Rent
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hello {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, {{ params.org_name }} here. We haven't received your rent that was due on {{ traits.{{ params.rent_due_trait }} | date: \"%-d %b %Y\" }}. Pay {{ params.pay_info }}. If something is in the way, please call {{ params.manager_phone }} so we can talk. Reply STOP to opt out."
  - ref: tmpl_soon_wa
    kind: template
    when: { param: use_whatsapp, equals: true }
    about: WhatsApp reminder before the rent is due (needs Meta approval).
    body:
      name: Rent due soon (WhatsApp)
      channel: whatsapp
      category: Rent
      style: text
      status: active
      subject: ""
      provider_template_id: rent_due_soon_reminder
      metadata: { whatsapp: { category: UTILITY, language: en } }
      variables:
        "1": "{{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}"
        "2": "{{ params.org_name }}"
        "3": "{{ traits.{{ params.rent_due_trait }} | date: \"%-d %b %Y\" }}"
        "4": "{{ params.pay_info }}"
        "5": "{{ params.manager_phone }}"
      body: "Hello {{1}}, a friendly reminder from {{2}}: your rent is due on {{3}}. Pay {{4}}. If you have already paid, thank you. Questions? Call {{5}} any time."
  - ref: tmpl_today_wa
    kind: template
    when: { param: use_whatsapp, equals: true }
    about: WhatsApp reminder on the due day (needs Meta approval).
    body:
      name: Rent due today (WhatsApp)
      channel: whatsapp
      category: Rent
      style: text
      status: active
      subject: ""
      provider_template_id: rent_due_today_reminder
      metadata: { whatsapp: { category: UTILITY, language: en } }
      variables:
        "1": "{{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}"
        "2": "{{ params.org_name }}"
        "3": "{{ params.pay_info }}"
        "4": "{{ params.manager_phone }}"
      body: "Hello {{1}}, this is {{2}}: your rent is due today. Pay {{3}}. If you have already paid, thank you. Questions? Call {{4}} any time."
  - ref: tmpl_overdue_wa
    kind: template
    when: { param: use_whatsapp, equals: true }
    about: WhatsApp message once rent is late (needs Meta approval).
    body:
      name: Rent overdue (WhatsApp)
      channel: whatsapp
      category: Rent
      style: text
      status: active
      subject: ""
      provider_template_id: rent_overdue_reminder
      metadata: { whatsapp: { category: UTILITY, language: en } }
      variables:
        "1": "{{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}"
        "2": "{{ params.org_name }}"
        "3": "{{ traits.{{ params.rent_due_trait }} | date: \"%-d %b %Y\" }}"
        "4": "{{ params.pay_info }}"
        "5": "{{ params.manager_phone }}"
      body: "Hello {{1}}, this is {{2}}. We have not yet received your rent that was due on {{3}}. Pay {{4}}. If something is in the way, please call {{5}} so we can talk about it."
  - ref: tmpl_lease_email
    kind: template
    about: Email when the lease is coming to an end.
    body:
      name: Lease ending soon (email)
      channel: email
      category: Lease
      style: html
      status: active
      subject: "Your tenancy with {{ params.org_name }}"
      metadata: { email: { from: "{{ params.email_from }}", preview_text: "Let's talk about what comes next." } }
      variables: {}
      body: >-
        <p>Hello {{ traits.first_name | default: "{{ params.greeting_fallback }}" }},</p>
        <p>Your tenancy with {{ params.org_name }} comes to an end on
        {{ traits.{{ params.lease_end_trait }} | date: "%-d %B %Y" }}. We would like to talk about
        whether you plan to stay, and what that would look like.</p>
        <p>Please reply to this email or call {{ params.manager_phone }}, and we will find a time.</p>
        <p>Thank you,<br>{{ params.org_name }}</p>
        <p style="font-size:12px;color:#6b6b80"><a href="{{ unsubscribe_url }}">Unsubscribe</a></p>

  - ref: wf_soon
    kind: workflow
    about: 'Daily run over "rent due soon": one reminder per period.'
    body:
      name: "Rent reminder 1: due soon"
      description: Every day, a reminder to tenants whose rent is due soon.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_rent_soon.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_soon_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_soon_wa.id }}" } } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 480
      goal: {}
      exit_conditions: []
  - ref: wf_today
    kind: workflow
    about: 'Daily run over "rent due today": one reminder.'
    body:
      name: "Rent reminder 2: due today"
      description: Every day, a reminder to tenants whose rent is due today.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_rent_today.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_today_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_today_wa.id }}" } } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 72
      goal: {}
      exit_conditions: []
  - ref: wf_overdue
    kind: workflow
    about: 'Daily run over "rent overdue": a message, then a call task for the manager if still unpaid.'
    body:
      name: "Rent reminder 3: overdue"
      description: Every day, a message to tenants whose rent is late, then a call task if they still haven't paid.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_rent_overdue.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_overdue_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_overdue_wa.id }}" } } } }
        - { id: s3, type: delay, config: { duration: 2d } }
        - { id: s4, type: filter, config: { condition: { type: trait, trait_key: "{{ params.rent_status_trait }}", cond_operator: neq, value: paid } } }
        - id: s5
          type: human_task
          config:
            human_task:
              title: "Late rent: {{ traits.first_name }} {{ traits.last_name }}, unit {{ traits.unit | default: \"not given\" }}"
              instructions: |-
                {{ traits.first_name }} {{ traits.last_name }} ({{ traits.phone }}) hasn't paid the rent that was due, even after a message.
                Call to check everything is OK and ask when they can pay. Be polite and factual, and follow your tenancy rules.
                Note what was agreed.
              assignee_refs: [{ type: team, id: "{{ ref.team_managers.id }}" }]
              strategy: round_robin
              completion_policy: first_response_wins
              outcome_options:
                - { id: paying, label: Will pay, intent: positive }
                - { id: discuss, label: Needs to talk about it, intent: neutral }
                - { id: not_reached, label: Could not reach, intent: negative }
              due_hours: 48
              wait: true
              wait_timeout_hours: 168
              notify: immediate
        - { id: s6, 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: 336
      goal: {}
      exit_conditions: []
  - ref: wf_lease
    kind: workflow
    about: 'Daily run over "lease ending soon": an email to the tenant and a renewal task for the manager.'
    body:
      name: "Lease ending: renewal talk"
      description: When a lease is about to end, an email to the tenant and a task to discuss renewal.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_lease_ending.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_lease_email.id }}" } } } }
        - id: s2
          type: human_task
          config:
            human_task:
              title: "Discuss renewal: {{ traits.first_name }} {{ traits.last_name }}, unit {{ traits.unit | default: \"not given\" }}"
              instructions: |-
                The lease for {{ traits.first_name }} {{ traits.last_name }} ({{ traits.phone }}) ends soon.
                Call to ask whether they want to renew, and agree the next step. Note what they said.
              assignee_refs: [{ type: team, id: "{{ ref.team_managers.id }}" }]
              strategy: round_robin
              completion_policy: first_response_wins
              outcome_options:
                - { id: renewing, label: Renewing, intent: positive }
                - { id: undecided, label: Still deciding, intent: neutral }
                - { id: moving_out, label: Moving out, intent: neutral }
                - { id: not_reached, label: Could not reach, intent: negative }
              due_hours: 72
              wait: true
              wait_timeout_hours: 240
              notify: immediate
        - { id: s3, type: action, config: { action_type: set_trait, traits: { lease_conversation: "{{ properties.task_outcome | default: \"unknown\" }}" } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 2160
      goal: {}
      exit_conditions: []

eval:
  name: 84_real_estate_rent_and_lease_reminders
  mode: task
  vertical: ""
  prompt: >-
    I manage rental flats. Remind tenants by SMS two weeks before their rent is due, on the day,
    and again if it's late, and give my property manager a task to call when rent is more than
    a day late and still unpaid. Also email tenants two months before their lease ends and give
    the manager a task to talk about renewal. The dates are in rent_due_date and lease_end_date,
    and rent_status says paid or due.
  expect_artifacts: [segment, workflow, message_template]
  forbid_terms: [evict, legal action]
