# Origamy playbook spec v1. Reference: /playbooks/spec-format/
spec_version: 1
id: communities.new-member-welcome
title: New member welcome
sector: communities
source:
  kind: synthesized
  notes: >-
    No association, club or alumni network has built this with us yet. Written from the church
    first-timer journey (a join form starts a welcome, a call task with escalation, later
    touches and a weekly report) and the base feedback pattern (a short rating form where a low
    score gives a person a task). The tone is friendly and the steps are timed the way
    volunteer-run groups usually do it: a welcome, a personal hello within three days, a week-one
    "meet the community" note and a check-in after a month.
derived_from: church.first-timer-to-member
vocabulary:
  person: member
  people: members
  group: executive
summary: >-
  Gives every new member a proper welcome. A join form collects their details and chapter, and
  the member gets a welcome email and SMS with your code of conduct and group chat link. Your
  membership secretary gets a task to say hello within three days. A week later the member gets a
  "meet the community" note, and after a month a short check-in form. The executive gets a weekly
  count of new members for each chapter.
when_to_use:
  - You run an association, professional body, alumni network, club, faith group or diaspora group, and new members join by a form or at a meeting.
  - New members sign up and then hear nothing, and some never make it into the group chat or to an event.
  - Your membership secretary wants a reminder to say hello, and your executive wants to see how many people joined each chapter.

requires:
  data_in:
    - how: public_form
      what: "The Join form and the Member Check-in form this playbook creates. Put the Join link on your website, in your bio, on a QR code at meetings and in the group chat. A volunteer can also fill it in with someone at an event."
  channels:
    - channel: email
      for: The welcome, the week-one note and the check-in
      needs: A verified sending domain for {{ params.email_from }}.
    - channel: sms
      for: The same three messages, so members without email still get them
      needs: An SMS provider, or Origamy managed SMS after business verification (KYC).
  events:
    - onboarding.submitted
  traits:
    - chapter
    - membership_stage
    - member_checkin_rating
    - welcome_call
    - welcome_note

parameters:
  - key: org_name
    ask: true
    question: What should messages call your association or group?
    label: Group name
    type: string
    example: Northwind Alumni Network
  - key: chapters
    ask: true
    question: What chapters or branches can members join? (one per line)
    help: The Join form shows these as choices, and the weekly report counts new members for each one. Use one entry, such as "Main", if you have no chapters.
    label: Chapters
    type: list
    default: [Main chapter]
  - key: secretary_team
    ask: true
    question: Which team says hello to new members? (usually the membership secretary, one person is fine)
    label: Membership secretary
    type: team_name
    default: Membership secretary
  - key: group_url
    ask: true
    question: What is the invite link to your WhatsApp or Telegram group?
    help: New members get it in the welcome. Use a link you can change later if it gets passed around.
    label: Group chat link
    type: url
    example: "https://chat.example.com/join"
  - key: conduct_url
    ask: true
    question: Where is your code of conduct? (a link to the page or document)
    label: Code of conduct link
    type: url
    example: "https://www.example.com/code-of-conduct"
  - key: events_url
    label: Link to your next events (leave empty to skip it)
    type: url
    default: ""
  - key: interests
    label: Interests members can tick on the Join form
    type: list
    default: [Events and meetups, Volunteering, Mentoring, Networking]
  - key: executive_team
    label: Who gets the weekly report and hears about a hello that wasn't made
    type: team_name
    default: Executive committee
  - key: call_due_hours
    label: Hours the secretary has to say hello
    type: hours
    default: 72
  - key: escalate_after_hours
    label: Hours after which an unmade hello goes to the executive
    type: hours
    default: 96
  - key: meet_delay
    label: Week-one note after joining
    type: duration
    default: 7d
  - key: checkin_delay
    label: Check-in after joining
    type: duration
    default: 30d
  - key: low_score
    label: A check-in score at or below this (out of 5) gives the secretary a task
    type: number
    default: 2
  - key: report_cron
    label: Weekly report
    type: cron
    default: "0 8 * * 1"
  - key: timezone
    label: Time zone for the report
    type: timezone
    default: "{{ workspace.timezone }}"
  - key: default_country
    label: Default phone country on the forms
    type: country_code
    default: "{{ workspace.country }}"
  - 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: friend

timings:
  - The welcome email and SMS go out as soon as someone submits the Join form.
  - A task for {{ params.secretary_team }} is created straight away, due in {{ params.call_due_hours }} hours, with reminders before it is due. If nobody has responded after {{ params.escalate_after_hours }} hours it escalates to {{ params.executive_team }}.
  - "The week-one note goes {{ params.meet_delay }} after joining. The check-in invitation goes {{ params.checkin_delay }} after joining, and only to members who haven't already filled it in."
  - A check-in score of {{ params.low_score }} or lower gives {{ params.secretary_team }} a task, due in 48 hours.
  - "The weekly report to {{ params.executive_team }} runs on cron {{ params.report_cron }} in {{ params.timezone }}."

customer_must:
  - Add your membership secretary to the {{ params.secretary_team }} team and your executive to {{ params.executive_team }} (Settings → Collaborators → Teams). Tasks and reports sent to an empty team reach nobody.
  - Review and publish the Join and Member Check-in forms. Publishing makes the links live; nothing sends. Check that the chapter list and interests are what you want members to choose from.
  - Verify the sending domain for the emails, and connect an SMS provider or finish business verification for managed SMS.
  - Make sure the group chat link works and that the code of conduct page is public. Both go into the welcome.
  - Tell members on the form that you will message them by email and SMS and that they can reply STOP, and keep their details only for running the group.
  - Switch the follow-ups and the weekly report on and test with your own phone and email by joining as a test member, then erase the test person.

objects:
  - ref: team_secretary
    kind: team
    about: Says hello to each new member and follows up low check-in scores.
    body: { name: "{{ params.secretary_team }}" }
  - ref: team_executive
    kind: team
    about: Hears about hellos that weren't made, and gets the weekly report.
    body: { name: "{{ params.executive_team }}" }

  - ref: form_join
    kind: form
    about: The join form. It saves the member's chapter, interests and consent, and starts them at the joined stage.
    body:
      name: Join
      slug: join
      description: "Join {{ params.org_name }}."
      draft_state:
        version: 1
        meta: { layout: classic, submit_label: Join, success_message: "Welcome to {{ params.org_name }}! Check your messages for the next steps." }
        pages:
          - id: p1
            title: "Join {{ params.org_name }}"
            description: Tell us a little about yourself. It takes a minute.
            fields:
              - { id: f_first, key: first_name, type: short_text, label: First name, required: true, maps_to: { mode: custom, key: first_name, trait: true } }
              - { id: f_last, key: last_name, type: short_text, label: Surname, required: true, 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: false, maps_to: { mode: mapped, slot: profile.email } }
              - id: f_chapter
                key: chapter
                type: single_select
                label: Which chapter are you joining?
                required: true
                maps_to: { mode: custom, key: chapter, trait: true }
                options:
                  $each: params.chapters
                  $value: { value: "{{ item }}", label: "{{ item }}" }
              - id: f_interests
                key: interests
                type: checkboxes
                label: What are you interested in?
                required: false
                maps_to: { mode: custom, key: interests }
                options:
                  $each: params.interests
                  $value: { value: "{{ item }}", label: "{{ item }}" }
              - id: f_heard
                key: how_heard
                type: single_select
                label: How did you hear about us?
                required: false
                maps_to: { mode: custom, key: how_heard }
                options:
                  - { value: friend, label: A friend or member }
                  - { value: social, label: Social media }
                  - { value: event, label: An event or meeting }
                  - { value: other, label: Somewhere else }
              - { id: f_conduct, key: agrees_to_conduct, type: boolean, label: I agree to follow the code of conduct, required: true, maps_to: { mode: custom, key: agrees_to_conduct } }
              - { id: f_stage, key: stage, type: hidden, label: "", required: false, default: joined, maps_to: { mode: custom, key: membership_stage, trait: true } }
      verification_settings: { required: false }
  - ref: form_checkin
    kind: form
    about: A short check-in form for members a month in. A hidden field moves them to the checked-in stage.
    body:
      name: Member Check-in
      slug: member-check-in
      description: "How is {{ params.org_name }} going for you?"
      draft_state:
        version: 1
        meta: { layout: classic, submit_label: Send, success_message: Thank you. We read every answer. }
        pages:
          - id: p1
            title: How is it going?
            description: Three quick questions. Your answers help us make the group better.
            fields:
              - { id: f_phone, key: phone, type: phone, label: Your phone number, required: true, maps_to: { mode: mapped, slot: profile.phone }, config: { country_picker: true, default_country: "{{ params.default_country }}", verify: "off" } }
              - { id: f_rating, key: rating, type: rating, label: "How is your experience of {{ params.org_name }} so far?", required: true, maps_to: { mode: custom, key: member_checkin_rating, trait: true }, validation: { min: 1, max: 5 } }
              - { id: f_more, key: wants_more, type: long_text, label: What would you like more of?, required: false, maps_to: { mode: custom, key: wants_more } }
              - { id: f_help, key: would_help, type: boolean, label: Would you like to help organise something?, required: false, maps_to: { mode: custom, key: would_help } }
              - { id: f_stage, key: stage, type: hidden, label: "", required: false, default: checked_in, maps_to: { mode: custom, key: membership_stage, trait: true } }
      verification_settings: { required: false }

  - ref: seg_joined
    kind: segment
    about: Members who joined and haven't filled in the check-in.
    body:
      name: New members, not yet checked in
      description: Stage is joined (set by the Join form).
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: membership_stage, cond_operator: eq, value: joined }
  - ref: seg_checked_in
    kind: segment
    about: Members who filled in the check-in.
    body:
      name: Members who have checked in
      description: Stage is checked_in (set by the Member Check-in form).
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: membership_stage, cond_operator: eq, value: checked_in }

  - ref: tmpl_welcome_email
    kind: template
    about: Welcome email with the code of conduct and the group chat link.
    body:
      name: Welcome to the group (email)
      channel: email
      category: Welcome
      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 is how to get started." } }
      variables: {}
      body: >-
        <p>Hello {{ traits.first_name | default: "{{ params.greeting_fallback }}" }},</p>
        <p>Welcome to {{ params.org_name }}. We are glad you joined, and we look forward to
        meeting you.</p>
        <p>Here is how to get started:</p>
        <ul>
        <li><a href="{{ params.group_url }}">Join our group chat</a>, where news and events are shared.</li>
        <li><a href="{{ params.conduct_url }}">Read our code of conduct</a>. It is short, and it helps everyone feel at home.</li>
        </ul>
        <p>Someone from the membership team will say hello soon. If you have a question, just
        reply to this email.</p>
        <p>Warm regards,<br>{{ params.org_name }}</p>
        <p style="font-size:12px;color:#6b6b80"><a href="{{ unsubscribe_url }}">Unsubscribe</a></p>
  - ref: tmpl_welcome_sms
    kind: template
    about: Welcome SMS with the group chat link.
    body:
      name: Welcome to the group (SMS)
      channel: sms
      category: Welcome
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Welcome to {{ params.org_name }}, {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}! Join our group chat here: {{ params.group_url }} Someone from the membership team will say hello soon. Reply STOP to opt out."
  - ref: tmpl_meet_email
    kind: template
    about: Week-one email that introduces the community and the next events.
    body:
      name: Meet the community (email)
      channel: email
      category: Welcome
      style: html
      status: active
      subject: "Meet the {{ params.org_name }} community"
      metadata: { email: { from: "{{ params.email_from }}", preview_text: "A week in. Here is how to get involved." } }
      variables: {}
      body: >-
        <p>Hello {{ traits.first_name | default: "{{ params.greeting_fallback }}" }},</p>
        <p>You joined {{ params.org_name }} a week ago, and we would love to see you in person.
        The easiest way to get to know people is to come to one of our events.</p>
        {% if "{{ params.events_url }}" != "" %}<p><a href="{{ params.events_url }}">See what is coming up</a></p>{% endif %}
        <p>You can also say hello in the <a href="{{ params.group_url }}">group chat</a>, or reply
        to this email and tell us what you would like to be part of.</p>
        <p>Warm regards,<br>{{ params.org_name }}</p>
        <p style="font-size:12px;color:#6b6b80"><a href="{{ unsubscribe_url }}">Unsubscribe</a></p>
  - ref: tmpl_meet_sms
    kind: template
    about: Week-one SMS that points to the events and the group chat.
    body:
      name: Meet the community (SMS)
      channel: sms
      category: Welcome
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "{{ params.org_name }}: hello {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, a week in! The easiest way to meet people is to come to an event.{% if \"{{ params.events_url }}\" != \"\" %} See what is coming up: {{ params.events_url }}{% endif %} Or say hello in the group chat: {{ params.group_url }} Reply STOP to opt out."
  - ref: tmpl_checkin_email
    kind: template
    about: Email that invites the member to the check-in form a month in.
    body:
      name: Check-in (email)
      channel: email
      category: Check-in
      style: html
      status: active
      subject: "How is {{ params.org_name }} going for you?"
      metadata: { email: { from: "{{ params.email_from }}", preview_text: "Three quick questions." } }
      variables: {}
      body: >-
        <p>Hello {{ traits.first_name | default: "{{ params.greeting_fallback }}" }},</p>
        <p>It has been a month since you joined {{ params.org_name }}. We would like to know how
        it is going, and what we could do better.</p>
        <p><a href="{{ ref.form_checkin.url }}">Answer three quick questions</a></p>
        <p>Thank you,<br>{{ params.org_name }}</p>
        <p style="font-size:12px;color:#6b6b80"><a href="{{ unsubscribe_url }}">Unsubscribe</a></p>
  - ref: tmpl_checkin_sms
    kind: template
    about: SMS that invites the member to the check-in form a month in.
    body:
      name: Check-in (SMS)
      channel: sms
      category: Check-in
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "{{ params.org_name }}: hello {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, a month in! How is it going? Please answer three quick questions: {{ ref.form_checkin.url }} Reply STOP to opt out."

  - ref: q_joined_by_chapter
    kind: saved_question
    about: New members for each chapter (counts only), for the weekly report.
    body:
      name: "Report: new members by chapter"
      data_plane_id: default
      viz_type: bar
      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_join.id }}" }
        aggregations: [{ fn: distinct, field: canonical_user_id, alias: members }]
        breakouts: [{ field: properties.chapter }]
  - ref: q_joined_total
    kind: saved_question
    about: How many people joined (counts only), for the weekly report.
    body:
      name: "Report: new members"
      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_join.id }}" }
        aggregations: [{ fn: distinct, field: canonical_user_id, alias: members }]

  - ref: report_weekly
    kind: report
    about: Each week, new members for each chapter, to the executive.
    body:
      name: "Weekly: new members"
      description: How many people joined this week, for each chapter, and how the welcome tasks went.
      schedule_cron: "{{ params.report_cron }}"
      timezone: "{{ params.timezone }}"
      enabled: false
      spec:
        period: last_7_days
        compare: previous_period
        week_start: monday
        sections:
          - { id: total, kind: saved_question, title: New members this week, viz: number, params: { question_id: "{{ ref.q_joined_total.id }}" } }
          - { id: by_chapter, kind: saved_question, title: New members by chapter, viz: bar, params: { question_id: "{{ ref.q_joined_by_chapter.id }}" } }
          - id: stages
            kind: segment_sizes
            title: Where members are now
            params: { segment_ids: ["{{ ref.seg_joined.id }}", "{{ ref.seg_checked_in.id }}"] }
          - { id: tasks, kind: tasks_summary, title: Welcome calls and messages, params: {} }
      recipients: [{ type: team, id: "{{ ref.team_executive.id }}" }]
      channel_policy: { mode: email_only }

  - ref: wf_welcome
    kind: workflow
    about: A welcome email and SMS the moment someone joins.
    body:
      name: "Member 1: welcome"
      description: When the Join form is submitted, a welcome email and SMS with the group chat link and code of conduct.
      enabled: false
      trigger_type: event
      trigger_config: { event_name: onboarding.submitted, form_id: "{{ ref.form_join.id }}" }
      steps:
        - { id: s1, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: email, template_id: "{{ ref.tmpl_welcome_email.id }}" } } } }
        - { id: s2, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_welcome_sms.id }}" } } } }
      re_entry_policy: once
      re_entry_cooldown_hours: 24
      goal: {}
      exit_conditions: []
  - ref: wf_hello
    kind: workflow
    about: A task for the membership secretary to say hello, escalated to the executive if nobody does. The outcome is saved.
    body:
      name: "Member 2: say hello"
      description: A hello task for the membership secretary, with reminders and escalation. The outcome and note are saved on the member.
      enabled: false
      trigger_type: event
      trigger_config: { event_name: onboarding.submitted, form_id: "{{ ref.form_join.id }}" }
      steps:
        - id: s1
          type: human_task
          config:
            human_task:
              title: "Say hello: {{ traits.first_name }} {{ traits.last_name }}, {{ traits.chapter | default: \"no chapter\" }}"
              instructions: |-
                {{ traits.first_name }} {{ traits.last_name }} ({{ traits.phone }}) has just joined the group.
                Chapter: {{ properties.chapter | default: "not given" }}. Interests: {{ properties.interests | default: "not given" }}. Heard about us: {{ properties.how_heard | default: "not given" }}.

                Send a warm personal message or call to say welcome. Make sure they are in the group chat, and ask if there is anything they would like to be part of. Choose an outcome and add a short note.
              assignee_refs: [{ type: team, id: "{{ ref.team_secretary.id }}" }]
              strategy: round_robin
              completion_policy: first_response_wins
              outcome_options:
                - { id: welcomed, label: Said hello, intent: positive }
                - { id: in_chat, label: Said hello and added to the group chat, intent: positive }
                - { id: not_reached, label: Could not reach, intent: negative }
              due_hours: "{{ params.call_due_hours }}"
              wait: true
              wait_timeout_hours: 240
              notify: immediate
              escalation_ladder:
                - { after_hours: "{{ params.escalate_after_hours }}", to: [{ type: team, id: "{{ ref.team_executive.id }}" }] }
        - { id: s2, type: action, config: { action_type: set_trait, traits: { welcome_call: "{{ properties.task_outcome | default: \"unknown\" }}", welcome_note: "{{ properties.task_note }}" } } }
      re_entry_policy: once
      re_entry_cooldown_hours: 24
      goal: {}
      exit_conditions: []
  - ref: wf_meet
    kind: workflow
    about: A week-one email and SMS that introduce the community and the next events.
    body:
      name: "Member 3: meet the community"
      description: A week after joining, an email and SMS about the next events and the group chat.
      enabled: false
      trigger_type: event
      trigger_config: { event_name: onboarding.submitted, form_id: "{{ ref.form_join.id }}" }
      steps:
        - { id: s1, type: delay, config: { duration: "{{ params.meet_delay }}" } }
        - { id: s2, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: email, template_id: "{{ ref.tmpl_meet_email.id }}" } } } }
        - { id: s3, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_meet_sms.id }}" } } } }
      re_entry_policy: once
      re_entry_cooldown_hours: 24
      goal: {}
      exit_conditions: []
  - ref: wf_checkin
    kind: workflow
    about: A check-in invitation after a month, only for members who haven't already filled it in.
    body:
      name: "Member 4: month check-in"
      description: A month after joining, an email and SMS with the check-in form, unless the member has already filled it in.
      enabled: false
      trigger_type: event
      trigger_config: { event_name: onboarding.submitted, form_id: "{{ ref.form_join.id }}" }
      steps:
        - { id: s1, type: delay, config: { duration: "{{ params.checkin_delay }}" } }
        - { id: s2, type: filter, config: { condition: { type: trait, trait_key: membership_stage, cond_operator: eq, value: joined } } }
        - { id: s3, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: email, template_id: "{{ ref.tmpl_checkin_email.id }}" } } } }
        - { id: s4, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_checkin_sms.id }}" } } } }
      re_entry_policy: once
      re_entry_cooldown_hours: 24
      goal: {}
      exit_conditions: []
  - ref: wf_low_score
    kind: workflow
    about: A low check-in score gives the membership secretary a task to reach out.
    body:
      name: "Member 5: low check-in score"
      description: When a check-in score is at or below the line, a task for the membership secretary to reach out. The outcome is saved.
      enabled: false
      trigger_type: event
      trigger_config:
        event_name: onboarding.submitted
        form_id: "{{ ref.form_checkin.id }}"
        property_filters: [{ property: rating, cond_operator: lte, value: "{{ params.low_score }}" }]
        properties: { $origamy_property_filters: true }
      steps:
        - id: s1
          type: human_task
          config:
            human_task:
              title: "Reach out: {{ traits.first_name }} {{ traits.last_name }}"
              instructions: |-
                {{ traits.first_name }} {{ traits.last_name }} ({{ traits.phone }}) rated the group {{ properties.rating }} out of 5 at their one-month check-in.
                What they would like more of: {{ properties.wants_more | default: "not said" }}.

                Reach out kindly, listen, and see whether something can be put right. Choose an outcome and add a short note.
              assignee_refs: [{ type: team, id: "{{ ref.team_secretary.id }}" }]
              strategy: round_robin
              completion_policy: first_response_wins
              outcome_options:
                - { id: resolved, label: Talked and sorted, intent: positive }
                - { id: follow_up, label: Needs more follow-up, intent: neutral }
                - { id: not_reached, label: Could not reach, intent: negative }
              due_hours: 48
              wait: true
              wait_timeout_hours: 168
              notify: immediate
        - { id: s2, type: action, config: { action_type: set_trait, traits: { checkin_follow_up: "{{ properties.task_outcome | default: \"unknown\" }}" } } }
      re_entry_policy: every_time
      re_entry_cooldown_hours: 0
      goal: {}
      exit_conditions: []

eval:
  name: 111_communities_new_member_welcome
  mode: task
  vertical: ""
  prompt: >-
    We run an alumni association with several chapters. Make a join form with name, phone, email,
    chapter, interests and how they heard about us. When someone joins, send a welcome email with
    our code of conduct and WhatsApp group link, and give our membership secretary a task to say
    hello within three days. A week later email them about our next events, and after a month
    send them a short check-in form. Send our executive a weekly report of new members for each
    chapter.
  expect_artifacts: [form, segment, workflow, message_template, team, report]
  forbid_terms: [discount, cart, checkout, purchase, revenue, customers]
