# Origamy playbook spec v1. Reference: /playbooks/spec-format/
spec_version: 1
id: professional-services.document-collection
title: Document collection before a deadline
sector: professional-services
source:
  kind: synthesized
  notes: >-
    No firm has built this with us yet. Written from the base patterns: a status trait that a
    form sets and a segment excludes (as in the payment reminder), one-off and weekly schedules
    from the rrule rules, and a call task with the outcome saved. Messages never name the
    documents' contents or any figures.
derived_from: base.payment-reminder
vocabulary:
  person: client
  people: clients
  group: team
summary: >-
  Asks every client for their documents each week until a deadline, with a link where they upload
  photos or PDFs. Anyone who hasn't uploaded when the final week starts goes on a call list for
  your team. Your team marks a client's documents complete once reviewed.
when_to_use:
  - Accountants, tax advisers, immigration consultants or bookkeepers who need the same set of documents from many clients by a date.
  - Clients send things late, by WhatsApp photo or email attachment, and you spend days chasing.
  - You want one upload page, and a clear list of who is still missing.

requires:
  data_in:
    - how: csv_import
      what: "Your client list as a CSV: phone, email, first name, and a documents_status column set to pending. Imported once for each round of collection."
    - how: sheet_sync
      what: "Or the client list as a synced sheet. If you sync, update the sheet's documents_status when clients upload, because every sync writes the sheet's value over the profile."
    - how: public_form
      what: "The Upload your documents form this playbook creates, with a file question for each document you need. Clients open it from a link in the reminders."
    - how: staff_form
      what: "A Documents reviewed form your team fills when a client's documents are checked, matched by phone."
  channels:
    - channel: email
      for: The weekly reminder
      needs: A verified sending domain for {{ params.email_from }}.
    - channel: sms
      for: The weekly reminder, shorter
      needs: An SMS provider, or Origamy managed SMS after business verification (KYC).
  events:
    - onboarding.submitted
  traits:
    - documents_status
    - document_call
    - document_call_note

parameters:
  - key: org_name
    ask: true
    question: What should messages call your firm?
    label: Firm name
    type: string
    example: Northwind Advisory
  - key: documents
    ask: true
    question: Which documents do you need? (one upload question each on the form)
    help: One per line. A short id (letters, no spaces) and the name clients will see.
    label: Documents
    type: list
    item_fields:
      key: { type: string, help: "short id, e.g. id_card" }
      name: { type: string, help: "what clients see, e.g. Valid ID" }
    default:
      - { key: id_card, name: Valid ID }
      - { key: bank_statements, name: Bank statements }
      - { key: income_records, name: Income records }
  - key: deadline_label
    ask: true
    question: What is the deadline? (as you'd say it in a message)
    label: Deadline
    type: string
    example: 15 January
  - key: first_reminder_at
    ask: true
    question: When should the first weekly reminder go out?
    help: A date and time like 2026-11-16T09:00 in your time zone. Reminders then repeat every week.
    label: First reminder (date and time)
    type: string
    example: 2026-11-16T09:00
  - key: final_call_at
    ask: true
    question: When should the call list be created for clients who still haven't uploaded? (the start of the final week)
    help: A date and time like 2026-12-28T09:00 in your time zone.
    label: Final-week call list (date and time)
    type: string
    example: 2026-12-28T09:00
  - key: accountant_team
    ask: true
    question: Which team calls clients who are still missing documents? (it can be just you)
    label: Call team
    type: team_name
    default: Accounts
  - key: reminder_weeks
    label: Number of weekly reminders
    type: number
    default: 6
  - key: what_for
    label: What the documents are for, as messages say it
    type: string
    default: your filing
  - key: email_from
    label: Sender address
    type: email
    default: "{{ workspace.default_sender }}"
  - key: call_due_hours
    label: Hours to make each call
    type: hours
    default: 48
  - key: timezone
    label: Time zone for the schedules
    type: timezone
    default: "{{ workspace.timezone }}"
  - key: default_country
    label: Default phone country on the forms
    type: country_code
    default: "{{ workspace.country }}"
  - key: greeting_fallback
    label: Greeting when a first name is missing
    type: string
    default: there

timings:
  - "Weekly reminders by email and SMS, starting {{ params.first_reminder_at }} ({{ params.timezone }}), {{ params.reminder_weeks }} of them, to every client whose documents_status is still pending."
  - Clients who upload stop being reminded straight away (the upload form changes their status). Clients whose documents your team marks complete stop too.
  - "On {{ params.final_call_at }} a call task is created for each client still pending, due in {{ params.call_due_hours }} hours."

customer_must:
  - "Import your client list as a CSV with phone, email, first name and documents_status = pending, for the clients who owe documents. Only clients with pending are reminded. If you sync a sheet instead, remember the sync overwrites documents_status."
  - Add people to the {{ params.accountant_team }} team. A task assigned to an empty team reaches nobody.
  - Publish the Upload your documents form (check each document question, and the file types and size limit) and the Documents reviewed form. Bookmark the review form for your team.
  - "Check the dates: the first reminder, how many weeks to repeat, and the final-week call list. They are one-off settings, so for next year's deadline run the playbook again with new dates."
  - Verify the sending domain, connect an SMS provider or finish business verification, then switch the follow-ups on. Test with your own phone and a test client set to pending.
  - Tell clients where their files go and who can see them, and follow your local data protection rules for identity documents.

objects:
  - ref: team_accountants
    kind: team
    about: Calls clients who are still missing documents in the final week.
    body: { name: "{{ params.accountant_team }}" }

  - ref: form_upload
    kind: form
    about: The upload form, with a file question for each document. A hidden field marks the client as having sent documents.
    body:
      name: Upload your documents
      slug: upload-documents
      description: "Send {{ params.org_name }} the documents we need for {{ params.what_for }}."
      draft_state:
        version: 1
        meta: { layout: classic, submit_label: Send documents, success_message: "Thank you. We'll check your documents and be in touch if anything is missing." }
        pages:
          - id: p1
            title: Upload your documents
            description: Photos or PDFs are fine. Upload what you have now and tell us if something is still to come.
            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_email, key: email, type: email, label: Your email address, required: false, maps_to: { mode: mapped, slot: profile.email } }
              - $each: params.documents
                $value: { id: "f_doc_{{ item.key }}", key: "doc_{{ item.key }}", type: file, label: "{{ item.name }}", required: false, maps_to: { mode: custom, key: "doc_{{ item.key }}" }, validation: { mime_types: [application/pdf, image/jpeg, image/png], max_size_mb: 10 } }
              - { id: f_missing, key: still_to_come, type: long_text, label: Anything still to come? Tell us what and when, required: false, maps_to: { mode: custom, key: still_to_come } }
              - { id: f_status, key: stage, type: hidden, label: "", required: false, default: received, maps_to: { mode: custom, key: documents_status, trait: true } }
      verification_settings: { required: false }
  - ref: form_reviewed
    kind: form
    about: Staff form to mark a client's documents as checked and complete. Matched by phone.
    body:
      name: Documents reviewed
      slug: documents-reviewed
      description: Staff only. One entry when a client's documents are checked and complete.
      draft_state:
        version: 1
        meta: { layout: classic, submit_label: Mark complete, success_message: Saved. }
        pages:
          - id: p1
            title: Documents reviewed
            description: Use the phone number on the client's record.
            fields:
              - { id: f_phone, key: phone, type: phone, label: Client's phone, required: true, maps_to: { mode: mapped, slot: profile.phone }, config: { country_picker: true, default_country: "{{ params.default_country }}", verify: "off" } }
              - { id: f_note, key: review_note, type: long_text, label: Note (nothing private), required: false, maps_to: { mode: custom, key: review_note } }
              - { id: f_status, key: stage, type: hidden, label: "", required: false, default: complete, maps_to: { mode: custom, key: documents_status, trait: true } }
      verification_settings: { required: false }

  - ref: seg_pending
    kind: segment
    about: Clients who owe documents (status pending).
    body:
      name: Documents pending
      description: documents_status is pending. Upload or review changes it, which removes the client.
      segment_type: dynamic
      status: active
      definition:
        operator: and
        conditions:
          - { type: trait, trait_key: documents_status, cond_operator: eq, value: pending }

  - ref: tmpl_email
    kind: template
    about: Weekly reminder email with the upload link.
    body:
      name: Documents reminder (email)
      channel: email
      category: Reminders
      style: html
      status: active
      subject: "Your documents for {{ params.what_for }}"
      metadata: { email: { from: "{{ params.email_from }}", preview_text: "" } }
      variables: {}
      body: >-
        <p>Hi {{ traits.first_name | default: "{{ params.greeting_fallback }}" }},</p>
        <p>{{ params.org_name }} still needs your documents for {{ params.what_for }}. Please send
        them by {{ params.deadline_label }}.</p>
        <p><a href="{{ ref.form_upload.url }}">Upload your documents</a></p>
        <p>The page lists everything we need. Photos from your phone are fine. If you've already
        sent them another way, reply and tell us and we'll update our records.</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_sms
    kind: template
    about: Weekly reminder SMS with the upload link.
    body:
      name: Documents reminder (SMS)
      channel: sms
      category: Reminders
      style: text
      status: active
      subject: ""
      metadata: {}
      variables: {}
      body: "Hi {{ traits.first_name | default: \"{{ params.greeting_fallback }}\" }}, {{ params.org_name }} still needs your documents by {{ params.deadline_label }}. Upload them here: {{ ref.form_upload.url }} Reply STOP to opt out."

  - ref: wf_weekly
    kind: workflow
    about: Weekly email and SMS to clients whose documents are pending, from the first reminder date.
    body:
      name: "Documents: weekly reminder"
      description: Every week until the deadline, an email and SMS to clients whose documents are still pending.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_pending.id }}", rrule: "FREQ=WEEKLY;COUNT={{ params.reminder_weeks }}", starts_at: "{{ params.first_reminder_at }}", 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, type: action, config: { action_type: send_message, channel_config: { mode: single, single: { channel: sms, template_id: "{{ ref.tmpl_sms.id }}" } } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 120
      goal: {}
      exit_conditions: []
  - ref: wf_final
    kind: workflow
    about: One run at the start of the final week; a call task for each client still pending.
    body:
      name: "Documents: final-week calls"
      description: Once, at the start of the final week, a call task for each client who still hasn't uploaded. The outcome and note are saved on the client.
      enabled: false
      trigger_type: schedule
      trigger_config: { segment_id: "{{ ref.seg_pending.id }}", rrule: FREQ=DAILY;COUNT=1, starts_at: "{{ params.final_call_at }}", timezone: "{{ params.timezone }}" }
      steps:
        - id: s1
          type: human_task
          config:
            human_task:
              title: "Documents missing: {{ traits.first_name }} {{ traits.last_name }}"
              instructions: |-
                {{ traits.first_name }} {{ traits.last_name }} ({{ traits.phone }}) still hasn't uploaded their documents and the deadline is {{ params.deadline_label }}.
                Call to find out what is stopping them and offer help (the upload link, or someone to walk them through it). Note what they said.
              assignee_refs: [{ type: team, id: "{{ ref.team_accountants.id }}" }]
              strategy: round_robin
              completion_policy: first_response_wins
              outcome_options:
                - { id: will_upload, label: Will upload, intent: positive }
                - { id: sent_other_way, label: Sent another way, 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: s2, type: action, config: { action_type: set_trait, traits: { document_call: "{{ properties.task_outcome | default: \"unknown\" }}", document_call_note: "{{ properties.task_note }}" } } }
      re_entry_policy: cooldown
      re_entry_cooldown_hours: 720
      goal: {}
      exit_conditions: []

eval:
  name: 90_professional_services_document_collection
  mode: task
  vertical: ""
  prompt: >-
    We're accountants and we need clients to send us their documents by 15 January. Make an
    upload page with places for their ID, bank statements and income records. Email and text
    everyone who hasn't uploaded every week, starting on Monday 16 November at 9am. On 28
    December, give our team a task to call anyone who still hasn't sent them. Our clients are in
    the documents_status trait as pending.
  expect_artifacts: [form, segment, workflow, message_template]
  forbid_terms: [discount, tax owed]
