## On this page

In a healthcare context, messages are sent all the time and can include many scenarios (patient to physician, physician to physician, and more), so ensuring they are well organized is important. This guide explains how to model and organize threads in Medplum.

Medplum messaging uses a two-level hierarchy of FHIR [`Communication`](/content/docs/api/fhir/resources/communication/index.html) resources: a thread header that represents the conversation, and child messages that hold the actual content. The same pattern supports provider-to-provider chat, patient-to-care-team messaging, and internal coordination.

This page covers:

- Representing individual messages and key [`Communication`](/content/docs/api/fhir/resources/communication/index.html) elements
- Thread architecture and how to structure thread headers and messages
- Tagging and grouping threads with `category`

## Thread Architecture [​](/content/docs/communications/messaging-data-model#thread-architecture "Direct link to Thread Architecture"/index.html)

A thread header has no `payload` and no `partOf`. It exists to group messages. Each message has both: `payload` with content and `partOf` pointing at the header. That distinction is how you tell headers apart from messages when querying the API (for example `part-of:missing=true` matches thread headers). For more query patterns and live updates, see [Searching and Querying Threads](/content/docs/communications/searching-and-querying-threads/index.html).

## Representing Individual Messages [​](/content/docs/communications/messaging-data-model#representing-individual-messages "Direct link to Representing Individual Messages"/index.html)

The FHIR [`Communication`](/content/docs/api/fhir/resources/communication/index.html) resource represents any healthcare message regardless of channel (email, SMS, in-app chat, and more).

| Element | Description | Relevant Valueset | Example |
| --- | --- | --- | --- |
| `payload` | Text, attachments, or resources communicated to the recipient. On messages this is often `contentString`, `contentAttachment`, or `contentReference`. Omit on thread headers. |  | You have an appointment scheduled for 2pm. |
| `sender` | The person or team that sent the message. |  | Practitioner/doctor-alice-smith |
| `recipient` | The person or team that receives the message; can list multiple for group threads. On the thread header, include the full participant list (see [Building and Structuring Threads](/content/docs/communications/messaging-data-model#building-and-structuring-threads/index.html)). |  | Practitioner/doctor-gregory-house |
| `topic` | The main focus of the conversation or message, similar to an email subject line. Put `topic` on the thread header for the thread title. Duplicating the same `topic` on every child message is **recommended** when you want message-level display, search, or subscriptions to carry the subject without reading the header; it is **not** required for a minimal valid thread. | Custom internal text | In person physical with Homer Simpson on April 10th, 2023 |
| `category` | Optional for basic threading. Use when you want a tag on the thread or message—for example filters, routing, or analytics. See [How to Tag or Group Threads](/content/docs/communications/messaging-data-model#how-to-tag-or-group-threads/index.html) for when to duplicate on children. | [HL7 Communication Category](http://terminology.hl7.org/CodeSystem/communication-category), SNOMED, custom | [See below](/content/docs/communications/messaging-data-model#how-to-tag-or-group-threads/index.html) |
| `reasonCode` | The specific reason the message was sent. Define a medical reason and/or a workflow reason: use the clinical findings subset of SNOMED for medical reasons and custom internal coding for workflow reasons. | [SNOMED Clinical Findings](http://hl7.org/fhir/R4/valueset-clinical-findings.html), custom internal | [301180005](https://browser.ihtsdotools.org/?perspective=full&conceptId1=301180005&edition=MAIN/2023-11-01&release=&languages=en) — Cardiovascular system normal (finding) |
| `partOf` | On a message, reference to the thread header [`Communication`](/content/docs/api/fhir/resources/communication/index.html). Empty on the thread header. |  | [See Building and Structuring Threads](/content/docs/communications/messaging-data-model#building-and-structuring-threads/index.html) |
| `inResponseTo` | Optional link to a specific prior message when the user explicitly replies to that message (for example a reply action). For linear chronological chat, `partOf` plus sorting by `sent` is usually enough. |  | Communication/previous-communication |
| `medium` | Channel or channels used; can be an array so one resource reflects multiple modalities. | [Participation Mode Codes](http://terminology.hl7.org/CodeSystem/v3-ParticipationMode) | email |
| `subject` | Patient or group the conversation is about. |  | Patient/homer-simpson |
| `encounter` | Optional link from the thread header to a session [`Encounter`](/content/docs/api/fhir/resources/encounter/index.html). See [Representing Asynchronous Encounters](/content/docs/communications/async-encounters/index.html). |  | Encounter/example-appointment |
| `sent` / `received` | When the message was sent or received. |  | 2023-04-10T10:00:00Z |
| `status` | Transmission and lifecycle state on the resource. Draft, sent, retracted, and related patterns are summarized in [Communication Lifecycle](/content/docs/communications/messaging-data-model#communication-lifecycle/index.html). For read tracking via Tasks, see [Read Receipts and Message Status](/content/docs/communications/read-receipts-and-message-status/index.html). | [Event Status Codes](http://hl7.org/fhir/R4/valueset-event-status.html) | in-progress |

`category` vs. `reasonCode`

`category` classifies the message at a broad level (for example notification versus alert). `reasonCode` explains why it was sent in more detail (for example appointment reminder versus abnormal lab result). A message can carry both, such as `category` for notification and `reasonCode` for appointment reminder.

For every search parameter, see the [Communication API reference](/content/docs/api/fhir/resources/communication#search-parameters/index.html).

## Communication Lifecycle [​](/content/docs/communications/messaging-data-model#communication-lifecycle "Direct link to Communication Lifecycle"/index.html)

| Stage | FHIR representation |
| --- | --- |
| Draft | `Communication.status` is `preparation`. See [Message Editing and Drafts](/content/docs/communications/message-editing-and-drafts/index.html). |
| Sent | `Communication.status` is `in-progress` and `Communication.sent` is populated when applicable. |
| Read | Varies based on requirements; see [Read Receipts and Message Status](/content/docs/communications/read-receipts-and-message-status/index.html). |
| Retracted | `Communication.status` is `entered-in-error` for retract-and-correct or similar workflows. See [Message Editing and Drafts](/content/docs/communications/message-editing-and-drafts/index.html). |

## Building and Structuring Threads [​](/content/docs/communications/messaging-data-model#building-and-structuring-threads "Direct link to Building and Structuring Threads"/index.html)

Beyond single messages, most products group messages into threads. In FHIR, use a two-level hierarchy: one parent [`Communication`](/content/docs/api/fhir/resources/communication/index.html) as the thread header and child [`Communication`](/content/docs/api/fhir/resources/communication/index.html) resources as messages. Children link to the header with `partOf`.

The header represents the thread, not a specific message. It has no `payload` and no `partOf`. Child messages include `payload` and `partOf` pointing at the header.

A minimal thread can use `topic` on the **header only** (for example [Creating Your First Thread](/content/docs/communications/creating-your-first-thread/index.html)); UIs usually show that title when rendering the message list. Duplicate the same `topic` on **each child** when you need each `Communication` to carry the subject for message-level search, subscriptions, or exports without joining the header.

### Example of a thread grouped using a Communication resource

```ts
{

resourceType: 'Communication',

id: 'example-thread-header',

// Thread header: no partOf or payload

// Include the thread creator in recipient so recipient-based inbox search finds threads they started

sender: { reference: 'Practitioner/doctor-alice-smith' },

recipient: [{ reference: 'Practitioner/doctor-alice-smith' }, { reference: 'Practitioner/doctor-gregory-house' }],

topic: {

text: 'Homer Simpson April 10th lab tests',

},

},

// The initial message

{

resourceType: 'Communication',

id: 'example-message-1',

payload: [\
\
    {\
\
      id: 'example-message-1-payload',\
\
      contentString: 'The specimen for your patient, Homer Simpson, has been received.',\
\
    },\
\
  ],

topic: {

text: 'Homer Simpson April 10th lab tests',

},

// ...

partOf: [\
\
    {\
\
      resource: {\
\
        resourceType: 'Communication',\
\
        id: 'example-thread-header',\
\
        status: 'completed',\
\
      },\
\
    },\
\
  ],

},

// A response directly to `example-message-1` but still referencing the parent communication

{

resourceType: 'Communication',

id: 'example-message-2',

payload: [\
\
    {\
\
      id: 'example-message-2-payload',\
\
      contentString: 'Will the results be ready by the end of the week?',\
\
    },\
\
  ],

topic: {

text: 'Homer Simpson April 10th lab tests',

},

// ...

partOf: [\
\
    {\
\
      resource: {\
\
        resourceType: 'Communication',\
\
        id: 'example-thread-header',\
\
        status: 'completed',\
\
      },\
\
    },\
\
  ],

inResponseTo: [\
\
    {\
\
      resource: {\
\
        resourceType: 'Communication',\
\
        id: 'example-message-1',\
\
        status: 'completed',\
\
      },\
\
    },\
\
  ],

},

// A second response

{

resourceType: 'Communication',

id: 'example-message-3',

payload: [\
\
    {\
\
      id: 'example-message-2-payload',\
\
      contentString: 'Yes, we will have them to you by Thursday.',\
\
    },\
\
  ],

topic: {

text: 'Homer Simpson April 10th lab tests',

},

// ...

partOf: [\
\
    {\
\
      resource: {\
\
        resourceType: 'Communication',\
\
        id: 'example-thread-header',\
\
        status: 'completed',\
\
      },\
\
    },\
\
  ],

inResponseTo: [\
\
    {\
\
      resource: {\
\
        resourceType: 'Communication',\
\
        id: 'example-message-2',\
\
        status: 'completed',\
\
      },\
\
    },\
\
  ],

},
```

## How to Tag or Group Threads [​](/content/docs/communications/messaging-data-model#how-to-tag-or-group-threads "Direct link to How to Tag or Group Threads"/index.html)

Tagging helps users interpret threads at a glance (for example a thread owned by nursing). Use `Communication.category` for that classification across purpose, audience, or nature.

You can omit `category` entirely if you do not need tags. **When using `category`,** put it on both the thread header and child [`Communication`](/content/docs/api/fhir/resources/communication/index.html) resources so message-level searches and filters stay consistent with the thread. `category` is an array, so each resource can carry multiple tags.

| Type of tag | Codesystem |
| --- | --- |
| Level of credential | [SNOMED Care Team Member Function valueset](https://vsac.nlm.nih.gov/valueset/2.16.840.1.113762.1.4.1099.30/expansion) |
| Clinical specialty | [SNOMED Care Team Member Function valueset](https://vsac.nlm.nih.gov/valueset/2.16.840.1.113762.1.4.1099.30/expansion) |
| Product offering | SNOMED, [LOINC](/content/docs/careplans/loinc/index.html), custom internal coding |

Example of multiple categories

```ts
{

resourceType: 'Communication',

id: 'example-communication',

status: 'completed',

category: [\
\
    {\
\
      text: 'Doctor',\
\
      coding: [\
\
        {\
\
          code: '158965000',\
\
          system: SNOMED,\
\
        },\
\
      ],\
\
    },\
\
    {\
\
      text: 'Endocrinology',\
\
      coding: [\
\
        {\
\
          code: '394583002',\
\
          system: SNOMED,\
\
        },\
\
      ],\
\
    },\
\
    {\
\
      text: 'Diabetes self-management plan',\
\
      coding: [\
\
        {\
\
          code: '735985000',\
\
          system: SNOMED,\
\
        },\
\
      ],\
\
    },\
\
  ],

};
```

Designing category schemes

You can use multiple `category` entries (for example separate entries for specialty and credential level). That tends to be self-documenting and easier to maintain, but queries may need to match several categories.

You can also use a single combined `category` code that encodes several dimensions. That can simplify search with one parameter and keep payloads smaller, but combinations explode over time and the app may need extra parsing.
