## Message Editing and Correction

The recommended approach for healthcare messaging is the "retract and correct" pattern: mark the original as an error, then create a new [`Communication`](/content/docs/api/fhir/resources/communication/index.html) with the corrected content. This preserves an explicit audit trail, which is important for clinical and compliance scenarios.

### Alternative for less regulated contexts
If your use case does not require explicit error marking (for example, internal team chat), you can update the [`Communication`](/content/docs/api/fhir/resources/communication/index.html)`payload` in place using `patchResource`. FHIR version history (`meta.versionId`) still preserves every previous version, so the audit trail exists — it is just not surfaced via `status`. Choose the approach that matches your compliance requirements.

The key difference is searchability: retract-and-correct makes edits discoverable via search (`Communication?status=entered-in-error` finds retracted messages, `Communication?category=correction` finds corrections). In-place updates make edit history only accessible via `_history`, which is not searchable. If you need to answer "show me all edited messages" without walking version history, use retract-and-correct.

### Retract the Original Message
Mark the original message as `entered-in-error`:

```ts
await medplum.patchResource('Communication', 'original-message-id', [
  { op: 'replace', path: '/status', value: 'entered-in-error' },
]);
```

### Create the Corrected Message
Create a new [`Communication`](/content/docs/api/fhir/resources/communication/index.html) with the corrected content. Use `inResponseTo` to link it to the original, and tag it with a `correction` category. Carry forward `topic`, `subject`, and `recipient` from the original message so the correction is discoverable via the same searches:

```ts
const originalSender = { reference: 'Practitioner/example-sender', display: 'Example Sender' };

const correctedMessage = await medplum.createResource({
  resourceType: 'Communication',
  status: 'in-progress',
  partOf: [{ reference: `Communication/${threadHeader.id}` }],
  topic: threadHeader.topic,
  subject: threadHeader.subject,
  recipient: [{ reference: 'Practitioner/doctor-gregory-house', display: 'Dr. Gregory House' }],
  inResponseTo: [{ reference: 'Communication/original-message-id' }],
  sender: originalSender,
  payload: [{ contentString: 'Updated: The appointment is at 3 PM, not 2 PM.' }],
  sent: new Date().toISOString(),
  category: [{
    coding: [{
      system: 'https://medplum.com/CodeSystem/communication-category',
      code: 'correction',
      display: 'Correction',
    }],
  },],
});

console.log(correctedMessage);
```

The original message content is always preserved in the resource's version history (`meta.versionId`), accessible via the Medplum App's History tab.

### Displaying Corrections
| Approach               | How it works |
|-----------------------|--------------|
| Hide + replace         | Filter out `entered-in-error` messages. Show the correction in the original's position (use `inResponseTo` to find the link). |
| Show with indicator    | Display the original with a strikethrough or "this message was edited" label. Show the correction below it. |

#### caution
`entered-in-error` messages still appear in search results unless you explicitly filter them out with `status:not=entered-in-error` in your message queries.

## Draft Messages
Draft messages let users compose a message and return to it later before sending.

### Single-Device Drafts (Browser Storage)
For the simplest case, save draft text to browser storage keyed by thread ID:

```ts
const draftStorageKey = `draft-${threadHeader.id}`;
const composedText = 'User composed text';
localStorage.setItem(draftStorageKey, composedText);
const restoredDraft = localStorage.getItem(draftStorageKey);
if (restoredDraft) {
  console.log(restoredDraft);
}
localStorage.removeItem(draftStorageKey);
```

In your UI, bind the restored string to your composer state (for example, a React `useState` setter) instead of logging it. This is fast and requires no server round-trips, but the draft is lost if the user switches devices or clears browser storage.

### Server-side rendering
If your app uses server-side rendering (for example, Next.js), guard `localStorage` access behind a `typeof window !== 'undefined'` check to avoid errors during SSR.

### Cross-Device Drafts (Server-Side)
For drafts that persist across devices, use `Communication.status = "preparation"`:

```ts
const draftBody = 'Draft body';
const serverDraft = await medplum.createResource({
  resourceType: 'Communication',
  status: 'preparation',
  partOf: [{ reference: `Communication/${threadHeader.id}` }],
  sender: { reference: `Practitioner/${currentUser.id}` },
  payload: [{ contentString: draftBody }],
});
console.log(serverDraft);
```

Update the draft as the user types (debounce to avoid excessive requests):

```ts
const draftResourceId = 'example-draft-communication-id';
const revisedDraftText = 'Revised draft body';
await medplum.patchResource('Communication', draftResourceId, [
  { op: 'replace', path: '/payload', value: [{ contentString: revisedDraftText }] },
]);
```

When the user sends the message, promote the draft. At this point, also ensure `topic`, `subject`, and `recipient` are populated if they were not set when the draft was first created:

```ts
await medplum.patchResource('Communication', 'example-draft-communication-id', [
  { op: 'replace', path: '/status', value: 'in-progress' },
  { op: 'add', path: '/sent', value: new Date().toISOString() },
  { op: 'add', path: '/topic', value: threadHeader.topic },
  { op: 'add', path: '/subject', value: threadHeader.subject },
  { op: 'add', path: '/recipient', value: [{ reference: 'Practitioner/doctor-gregory-house' }] },
]);
```

### Draft attachments
Draft [`Communication`](/content/docs/api/fhir/resources/communication/index.html) resources support the same `payload` types as sent messages, including `contentAttachment` and `contentReference`. If your composer allows file uploads before send, add the attachment to the draft's `payload` array alongside or instead of `contentString`.

### Loading Drafts
Query for the current user's drafts across all threads:

```ts
await medplum.searchResources('Communication', {
  sender: `Practitioner/${currentUser.id}`,
  status: 'preparation',
});
```

#### caution
Other users should never see your drafts. When loading messages for a thread, filter with `status:not=preparation` to exclude all drafts. Only load drafts via a sender-scoped query for the current user.

### Draft cleanup
Server-side drafts can accumulate over time if users start composing but never send. Set up a cron-triggered Bot to purge `preparation` [`Communication`](/content/docs/api/fhir/resources/communication/index.html) resources older than a threshold (for example, 30 days). For large numbers of stale drafts, batch the deletes using `medplum.executeBatch()` with a Transaction Bundle for better performance.

```ts
const staleDraftCutoff = new Date();
staleDraftCutoff.setDate(staleDraftCutoff.getDate() - 30);
const staleDrafts = await medplum.searchResources('Communication', {
  status: 'preparation',
  _lastUpdated: `lt${staleDraftCutoff.toISOString()}`,
});
for (const stale of staleDrafts) {
  await medplum.deleteResource('Communication', stale.id);
}
console.log(staleDrafts.length);
```

## In Your UI
For corrections, choose one of the two display approaches in the table above. If you use "hide + replace," query with `status:not=entered-in-error` and use the correction's `inResponseTo` reference to position it where the original appeared. If you use "show with indicator," render the retracted message with a visual label (for example strikethrough text or an "edited" badge) and display the correction inline below it.

For drafts, show an auto-saving indicator in the composer when using server-side drafts so users know their work is persisted. In the thread list, consider showing a "draft" badge next to threads where the current user has an unsent `preparation` Communication so they can resume composing.
