# Origamy playbook spec v1. Reference: /playbooks/spec-format/
spec_version: 1
id: base.birthday-anniversary
title: Birthday or anniversary
sector: base
source:
  kind: engagement
  date: 2026-10-22
  ref: eng-2026-10-schools-a
  notes: >-
    A school's birthday journey, fixed on 2026-10-22: its first rule compared full dates with
    less-than and greater-than and matched nobody. What works is a "same day of the year"
    condition with an explicit time zone, a daily schedule, and a once-a-year cooldown. A church
    build adds the year-less birthday question (month and day only) on its forms.
derived_from: null
vocabulary:
  person: person
  people: people
  group: team
summary: >-
  Sends a birthday (or joining-anniversary) message on the day, every year, in your time zone.
  Optionally gives a team a short list of today's birthdays to call.
when_to_use:
  - You keep a birthday, a joining date or another yearly date on each profile.
  - A personal message on the day fits your relationship with people.

requires:
  data_in:
    - how: public_form
      what: "A birthday question (month and day) on any form you already use."
    - how: sheet_sync
      what: "Or a synced sheet or CSV with a birthday column."
  channels:
    - channel: email
      for: The birthday email
      needs: A verified sending domain for {{ params.email_from }}.
    - channel: sms
      for: The birthday SMS (optional)
      needs: An SMS provider, or Origamy managed SMS after business verification (KYC).
  events: []
  traits:
    - "{{ params.date_trait }}"

parameters:
  - key: org_name
    ask: true
    question: What should messages call your organisation?
    label: Organisation name
    type: string
    example: Northwind
  - key: occasion
    ask: true
    question: Is this for birthdays or for an anniversary (like the date someone joined)?
    label: Occasion
    type: enum
    options: [birthday, anniversary]
    default: birthday
  - key: date_trait
    ask: true
    question: Which trait holds the date? Year-less dates (month and day) work too.
    label: Date trait
    type: trait_key
    default: birthday
  - key: send_time
    ask: true
    question: What time of day should it go out?
    help: 07:00 or later. The segment refreshes within about an hour after local midnight, and the date check uses the segment's time zone (a step condition would use UTC).
    label: Send time
    type: time
    default: "08:00"
  - key: send_sms
    ask: true
    question: Send an SMS as well as the email?
    label: SMS too
    type: boolean
    default: false
  - key: call_team
    ask: true
    question: Should a team get a task to call each person? Which team? (leave empty for no calls)
    label: Call team
    type: team_name
    default: ""
  - key: email_from
    label: Sender address
    type: email
    default: "{{ workspace.default_sender }}"
  - key: timezone
    label: Time zone ("today" starts at local midnight)
    type: timezone
    default: "{{ workspace.timezone }}"
  - key: greeting_fallback
    label: Greeting when a first name is missing
    type: string
    default: friend

timings:
  - Every day at {{ params.send_time }} in {{ params.timezone }}, everyone whose {{ params.date_trait }} falls on today's month and day gets the message.
  - Each person gets it at most once a year (a 300-day cooldown, as the calendar's own birthday journeys use). 29 February birthdays are celebrated on 1 March in other years.

customer_must:
  - Fill {{ params.date_trait }} on profiles. A form date question set to month and day only saves it without the year, plus birth_month and birth_day.
  - Verify the sending domain; for SMS, connect a provider or finish business verification.
  - Switch the follow-up on, and check the segment on a day you know someone has a birthday. A follow-up only runs once it's on, so test with a test person whose date is today.

objects:
  - ref: team_callers
    kind: team
    when: { param: call_team, not_empty: true }
    about: Calls people on their day.
    body: { name: "{{ params.call_team }}" }
  - ref: seg_today
    kind: segment
    about: Everyone whose date falls on today's month and day, in your time zone.
    body:
      name: "{{ params.occasion }} today"
      description: "{{ params.date_trait }} has today's month and day (year ignored), in {{ params.timezone }}."
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: "{{ params.date_trait }}", cond_operator: day_of_year_eq, value: today, timezone: "{{ params.timezone }}" }
          # an anniversary skips people who joined this year
          - { when: { param: occasion, equals: anniversary }, type: trait, trait_key: "{{ params.date_trait }}", cond_operator: older_than, value: 300d }
  - ref: tmpl_email
    kind: template
    about: The birthday or anniversary email.
    body:
      name: "Happy {{ params.occasion }} (email)"
      channel: email
      category: Occasions
      style: html
      status: active
      subject: "Happy {{ params.occasion }}, {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}!"
      metadata: { email: { from: "{{ params.email_from }}", preview_text: "From all of us at {{ params.org_name }}" } }
      variables: {}
      body: >-
        <p>Dear {{ traits.first_name | default: "{{ params.greeting_fallback }}" }},</p>
        <p>Happy {{ params.occasion }} from all of us at {{ params.org_name }}! We hope your day is
        a wonderful one.</p>
        <p>With warm wishes,<br>{{ params.org_name }}</p>
        <p style="font-size:12px;color:#6b6b80"><a href="{{ unsubscribe_url }}">Unsubscribe</a></p>
  - ref: tmpl_sms
    kind: template
    when: { param: send_sms, equals: true }
    about: The birthday or anniversary SMS.
    body:
      name: "Happy {{ params.occasion }} (SMS)"
      channel: sms
      category: Occasions
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Happy {{ params.occasion }}, {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}! Warm wishes from all of us at {{ params.org_name }}. Reply STOP to opt out."
  - ref: wf_daily
    kind: workflow
    about: Every day at the send time, the message to everyone in today's segment.
    body:
      name: "Happy {{ params.occasion }}"
      description: "Every day, the {{ params.occasion }} message to everyone whose date is today."
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_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: email, template_id: "{{ ref.tmpl_email.id }}" } } } }
        - { id: s2, when: { param: send_sms, equals: true }, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_sms.id }}" } } } }
        - id: s3
          when: { param: call_team, not_empty: true }
          type: human_task
          config:
            human_task:
              title: "{{ params.occasion }} call: {{ traits.first_name }} {{ traits.last_name }}"
              instructions: "It's {{ traits.first_name }} {{ traits.last_name }}'s {{ params.occasion }} today. Give them a quick call on {{ traits.phone }} to wish them well."
              assignee_refs: [{ type: team, id: "{{ ref.team_callers.id }}" }]
              strategy: round_robin
              completion_policy: first_response_wins
              outcome_options:
                - { id: called, label: Called, intent: positive }
                - { id: not_reached, label: Could not reach, intent: negative }
              due_hours: 12
              wait: false
              notify: immediate
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 7200
      goal: {}
      exit_conditions: []

eval:
  name: 66_base_birthday
  mode: task
  vertical: ""
  prompt: >-
    Send everyone a happy birthday email at 8am on their birthday, in Lagos time. The birthday
    is in the date_of_birth trait. Only once a year per person.
  expect_artifacts: [segment, workflow, message_template]
  forbid_terms: []
