Secure messaging
Encrypted patient threads. Bodies AES-256-GCM at rest, attachments behind signed URLs.
Threads are owned by a contact and assigned to a staff user. Messages within a thread are managed through follow-up endpoints; today only thread CRUD is exposed publicly. archived=true on list filters in archived threads.
Open a thread
Create a new secure-message thread with a patient.
Required scopes:
contacts.writeRequest body
| Name | Type | Description |
|---|---|---|
contactIdrequired | string | Patient. |
subject | string | Thread subject. ≤ 500 chars. |
assigneeUserId | string | Staff user to route to. |
Request body
{ "contactId": "cln…", "subject": "Pre-visit questions" }Responses
201 CreatedCreated.example { "id": "smt_4k9d2x…", "contactId": "cln1f8s7p…", "subject": "Pre-visit questions", "assigneeUserId": "usr_8h2c3p…", "isArchived": false, "lastMessageAt": "2026-05-18T14:00:00Z", "createdAt": "2026-05-18T13:55:00Z" }
Code samples
curl -X POST https://app.contactfollowup.com/api/secure-messages/threads \
-H "Authorization: Bearer $HEARTHNOTE_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "contactId": "cln…", "subject": "Pre-visit questions" }'List threads
Filter by assignee, contact, archived flag.
Required scopes:
contacts.readQuery parameters
| Name | Type | Description |
|---|---|---|
assigneeUserId | string | Staff user filter. |
contactId | string | Patient filter. |
archived | boolean | Include archived threads. |
limit | integer | Default 100. |
Responses
200 OKThreads.example { "threads": [{ "id": "smt_4k9d2x…", "contactId": "cln1f8s7p…", "subject": "Pre-visit questions", "assigneeUserId": "usr_8h2c3p…", "isArchived": false, "lastMessageAt": "2026-05-18T14:00:00Z", "createdAt": "2026-05-18T13:55:00Z" }] }
Code samples
curl -X GET https://app.contactfollowup.com/api/secure-messages/threads \
-H "Authorization: Bearer $HEARTHNOTE_TOKEN" \
-H "Content-Type: application/json"