## Section 1: Use Case & Participants

These questions establish the "who and why" before scoping any features. The answers should shape how you frame the rest of the conversation.

**1.1 Who is messaging whom?**

- Patient ↔ provider  
- Provider ↔ provider  
- Care team internally (no patient in the thread)  
- System/automated → patient (one-way notifications)  
- Some combination of the above

_Why it matters: different participant types affect access control, thread structure, and whether a patient subject is always required on a thread._

**1.2 What is the primary purpose of messaging in their product?**

- Clinical care coordination (e.g. async visits, follow-ups)  
- Patient engagement / support  
- Internal operational communication  
- Outbound notifications only (reminders, results)

_Why it matters: determines how much of the feature set is actually needed and whether async encounters/billing will come up._

**1.3 Is this a new build or replacing something they have today?**

- If replacing: what are they replacing, and what are the gaps that drove this?  
- If new: is messaging central to the product or additive?

_Why it matters: existing systems often carry assumptions about data model or UX that will surface as requirements. Knowing this early prevents surprises._

## Section 2: Feature Scoping

_Go through each together. For each: Yes / No / Nice-to-have / Not sure._

| # | Feature | Yes | No | Nice-to-have | Not sure |
| --- | --- | --- | --- | --- | --- |
| 1 | Live / real-time updates (new messages appear without refresh) |  |  |  |  |
| 2 | Message response tracking and routing (assigning threads to providers or queues) |  |  |  |  |
| 3 | Read receipts and unread counts |  |  |  |  |
| 4 | File and image attachments |  |  |  |  |
| 5 | Message editing and drafts |  |  |  |  |
| 6 | Automated messaging (reminders, out-of-office, SLA escalations) |  |  |  |  |
| 7 | External channel delivery (SMS, email — beyond in-app) |  |  |  |  |
| 8 | Async encounters / billing |  |  |  |  |

## Section 3: Feature Deep Dives

_Cover each feature flagged Yes or Nice-to-have. The goal is to land on a clear recommended approach by the end of each section._

### 3.1 Thread Structure _(always covered)_

**Questions:**

- Are threads always tied to a specific patient, or do you need internal/admin threads with no patient context?  
- Are threads 1:1 only, or do you need group conversations with more than two participants?  
- Do threads need to be tagged or categorized (e.g. by specialty, product line, urgency)? If so, do tags need to be combinable (e.g. a thread tagged both "cardiology" and "urgent")?

**What the answers drive:**

| Situation | Approach |
| --- | --- |
| Threads always tied to a patient | Set `subject` on every thread header — enables filtering by patient across the inbox |
| Internal/admin threads needed | `subject` is optional — don't assume it's always present in queries or UI |
| 1:1 only | Simpler participant model; simplifies read receipt implementation (see 3.4) |
| Group threads needed | Recipient list on thread header must include all participants including the thread creator |
| Tags/categories needed, single dimension | Use a single `category` entry on both the thread header and child messages |
| Tags/categories needed, multiple combinable dimensions | Use multiple `category` entries (e.g. one for specialty, one for credential level) — more maintainable but queries may need to match several categories |

### 3.2 Live Updates

**Questions:**

- Do new messages need to appear in real time without a page refresh?  
- Is this needed for the patient-facing app, the provider-facing app, or both?

**What the answers drive:**

| Situation | Approach |
| --- | --- |
| Real-time updates needed | WebSocket subscriptions on the thread — requires this to be scoped and enabled for the project |
| Polling is acceptable | Simpler implementation; periodic re-fetch of thread messages |
| Notification only (e.g. badge, push) without live message rendering | Can use subscription to trigger a notification without requiring in-place message updates |

### 3.3 Routing & Assignment

**Questions:**

- Do messages get assigned to specific providers, to a pool/queue by role, or both?  
- Can threads be reassigned or escalated after initial assignment?  
- After reassignment, does the previous owner need to retain visibility of the thread?  
- Do you need a structured audit trail of who owned a thread and why it was rerouted?

**What the answers drive:**

| Situation | Approach |
| --- | --- |
| Individual assignment only | Set `Task.owner` to a specific provider; remove `performerType` |
| Pool/queue routing | Set `Task.performerType` by role type; clear `Task.owner`; providers claim from pool |
| Both | Use `performerType` for initial pool routing; set `owner` and clear `performerType` when claimed |
| Reassignment needed, previous owner loses access | Update `Task.owner` in place; update `Communication.recipient` to match |
| Reassignment needed, previous owner retains visibility | Create a new Task for the new owner; mark original as cancelled with a note pointing to the new Task |
| Audit trail needed (free text) | Append to `Task.note` on each reroute with author and timestamp |
| Audit trail needed (structured/queryable) | Create a `Provenance` resource on each reroute with a coded reason — queryable via `Provenance?target=Task/{id}` |

### 3.4 Read Receipts & Unread State

**Questions:**

- Are threads ever group conversations (more than 2 participants)?  
- Do you need to query unread counts via API — e.g. a badge count or a list of all unread threads for a user?  
- Do unread counts need to be tracked per-recipient in a group thread?

**What the answers drive:**

| Situation | Approach |
| --- | --- |
| 1:1 threads only | Option A: mark message `status = completed` when read; query unread via status filter. Simplest model. |
| Group threads, no API unread query needed | Option B: store per-participant read state as an extension on the thread header. Shows unread dot in UI; not searchable via API. |
| Group threads, API unread query needed | Option C: create a read-receipt Task per recipient per message; complete it when read. Supports badge counts and unread queries. Most complex. |

### 3.5 Attachments

**Questions:**

- What file types need to be supported?  
- Do attachments need to be searchable or referenceable as clinical documents — e.g. queryable by patient or document type — or are they just files to download within a message?

**What the answers drive:**

| Situation | Approach |
| --- | --- |
| Files are just downloads within a message | Store as `contentAttachment` on the message payload — simple, no separate resource needed |
| Attachments need to be clinical documents (searchable, referenceable by patient/type) | Create a `DocumentReference` and attach via `contentReference` on the payload — file becomes a first-class searchable clinical document. Note: this is the default behavior of Medplum's React `ThreadChat` component. |
| Attachments are references to existing clinical resources (e.g. a lab result) | Use `contentReference` pointing at the relevant FHIR resource (e.g. `DiagnosticReport`) |

### 3.6 Editing & Drafts

**Questions:**

- Can sent messages be corrected or retracted after sending?  
- If so, does the original message content need to be discoverable via search (e.g. "show all edited messages"), or is it sufficient for the history to exist but not be directly queryable?  
- Do users need to save a draft and return to it across sessions or devices, or is per-device/browser draft storage acceptable?

**What the answers drive:**

| Situation | Approach |
| --- | --- |
| Sent messages cannot be edited | No special handling needed |
| Sent messages can be corrected; edits need to be searchable | Retract-and-correct: mark original `status = entered-in-error`, create new message with corrected content linked via `inResponseTo` and tagged with a `correction` category. Corrections are discoverable via status and category filters. |
| Sent messages can be corrected; searchability of edits not required | In-place payload update via `patchResource`. Full version history still exists via `_history` but is not searchable. |
| Drafts only needed per browser/device | Store in browser `localStorage` keyed by thread ID — no server resources needed, but lost if user clears storage or switches devices |
| Drafts need to persist across devices/sessions | Store as `Communication` with `status = preparation`; update as user types (debounced); promote to `in-progress` with a `sent` timestamp when sent. Filter drafts out of thread message queries with `status:not=preparation`. |

### 3.7 Automations

**Questions:**

- Are there events that should trigger automated messages (e.g. new lab result, appointment reminder)?  
- Are there SLA or response-time requirements (e.g. all messages responded to within 4 hours)?  
- Do providers have out-of-office or availability states that should affect routing?  
- Do you need to report on SLA compliance or response time metrics?

**What the answers drive:**

| Situation | Approach |
| --- | --- |
| Event-triggered automation (e.g. send message when lab result arrives) | Bot triggered by a Subscription on the relevant resource type |
| Recurring automation (e.g. flag threads with no response after N days) | Cron-scheduled Bot that scans for stale threads and creates reminder Tasks |
| Out-of-office / availability-based rerouting | Bot triggered on new message checks provider Schedule; reroutes Task to pool if provider has no available slots |
| SLA reporting needed | Task data (`authoredOn`, status change timestamps, `owner`, `priority`) should be exported to an analytics platform — FHIR search is not designed for aggregate SLA metrics |

### 3.8 External Channels

**Questions:**

- Which channels — SMS, email, other?  
- Is this outbound-only (notify the patient) or does the patient reply back into the thread?  
- If inbound replies are needed, how is the patient identified from an incoming message (e.g. phone number, email address)?  
- Does the external provider (e.g. Twilio) have its own conversation ID that can be stored for thread matching?

**What the answers drive:**

| Situation | Approach |
| --- | --- |
| Outbound notifications only | App creates the Communication, then calls `executeBot` directly to send via the external channel — not Subscription-triggered, so errors surface synchronously |
| Inbound replies needed | Inbound webhook Bot receives the payload, identifies the patient, matches or creates a thread, creates a child Communication |
| External conversation ID available (e.g. Twilio conversation SID) | Upsert thread header by that identifier on first message; use it as conditional `partOf` reference on all inbound messages — most reliable thread matching strategy |
| No external conversation ID | Match thread by patient phone/email and open thread status; ambiguous cases need a defined fallback strategy |
| Round-trip (provider replies in-app, patient receives via SMS/email) | Store channel on thread header as `medium`; app creates the Communication then calls `executeBot` to dispatch outbound — Bot reads `medium` to route to the right channel |
| Webhook retry / duplicate messages likely | Use `createResourceIfNoneExist` with the provider's message ID as an identifier to prevent duplicate inbound Communications |

### 3.9 Async Encounters / Billing

**Questions:**

- Does the customer intend to bill for messaging-based care?  
- How do you define a "session" — one thread per session, grouped by day, or patient-initiated?  
- Can a single messaging session involve multiple patients (e.g. a parent asking about two children)?  
- Do you need to capture diagnosis codes, service type, or other clinical details per encounter?

**What the answers drive:**

| Situation | Approach |
| --- | --- |
| No billing intent | No Encounter resource needed; thread stands alone |
| Billing needed, one patient per session | Create an Encounter per session; link to the thread header via `Communication.encounter` |
| One thread = one session | Straightforward 1:1 mapping of thread to Encounter |
| Rolling interaction model (e.g. continuous text thread, grouped by day) | One Encounter spans multiple threads or a time window; multiple thread headers link to the same Encounter |
| Multiple patients can be in one session | Create a parent session Encounter + one child Encounter per patient via `Encounter.partOf`; clinical details live on child Encounters |
| Clinical documentation needed for billing | Populate `Encounter.participant`, `Encounter.reasonCode`, and `Encounter.serviceType` per patient Encounter |

## Related Integrations

The external messaging channels Medplum integrates with first-party — both deliver through FHIR `Communication` resources, so they slot into the thread model above:

- **[Twilio SMS](/content/docs/integration/twilio-sms/index.html)** — send and receive SMS via the `$send-sms-twilio` operation.  
- **[eFax](/content/docs/integration/efax/index.html)** — send and receive faxes via the `$send-efax` and `$receive-efax` operations.
