Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

The message store and search

Every message the engine accepts is stored in its Postgres database before the sender gets its ACK, as it was received, and kept for the interface's retention. That record is how you answer "did we get it, when, and what did each receiver get?", and it's where resend, replay and correction start from.

What's kept

KeptFor
The messageas received, byte for byte, with when it arrived, its ordering key (the MRN) and MSH-10the retention window (retention)
Each destination's deliveryits status, attempts, last error, when it changedthe same window
What was sentonly for destinations with save_output: true; otherwise it's made again by running the code when you lookthe same window
What people didevery resend, replay, edit, dismissal and payload read, with who and whyaudit.keep, six years by default

Only the original is stored by default. "What did the lab get?" is answered by running the lab's code on the stored original again, which gives what was sent as long as the code hasn't changed since. Where you must be able to prove what went out (a receiver that disputes it, a regulator), turn on save_output for that destination; it costs about 1.15 GB per million deliveries (nuance).

An HTTP source in respond mode stores nothing: its requests are answered from code, not queued (nuance).

Statuses

StatusA delivery isA message is
pendingwaiting to be sent, or being retriedwaiting for at least one destination
sentdelivered: AA, a 2xx, a complete file, stored in the next interfacefinished; at least one destination sent it, none quarantined it
filterednot sent on purpose: null, kill(), dismissed, replaced by a corrected copy, or a destination removedfinished; every destination filtered it
quarantinedheld for a person: refused (AR, a 4xx), the code threw, or max_attempts ran outfinished, but needs someone

A message's status is the worst of its destinations' (quarantined if any is), so "quarantined" on a message means some receiver didn't get it; the message's page says which.

Finding a message

In the console: an interface's Messages tab lists its messages newest first, filtered by status; a message's page shows the payload (with the payloads permission), each destination's outcome and the history. The Quarantine page lists every quarantined delivery across interfaces.

From the API: the same list, a page at a time:

$ curl -s -H "Authorization: Bearer $DUCTUS_API_TOKEN" \
    "http://127.0.0.1:7700/api/v1/interfaces/adt-in/messages?status=quarantined&limit=2"
{"messages":[{"id":2,"received_at":"2026-10-31T10:49:28.372890Z","interface":"adt-in","status":"quarantined","ordering_key":"QUARANTINE-ME2","control_id":"QQUARANTINE-ME2","size":117,"last_error":"lab: rejected (AR): rejected by the receiver","updated_at":"2026-10-31T10:49:28.382056Z","origin":null},{"id":1,…}],"next_before":1}

next_before is the next page's before. One message, with its payload and every destination's outcome, is GET /api/v1/messages/{id} (messages API).

Searching by content: not yet

There's no search by MRN, name or field in the console or the API yet. The message list filters by status and pages by time; a search across messages by patient and date is planned (v1.3, with long-term storage). Until then:

  • By the ordering key: each message keeps its ordering key's value (ordering_key, the MRN for ordering.key: PID-3.1) and MSH-10 (control_id) in their own columns. A DBA, or you with a read-only login, can find them in the database:
-- psql, with a read-only login (pg_read_all_data); one interface, one day
SELECT id, received_at, status, control_id
FROM messages
WHERE interface = 'adt-in'
  AND received_at >= '2026-10-30' AND received_at < '2026-10-31'
  AND ordering_key = '123456'
ORDER BY id;

The time range matters: messages are partitioned by time, and with it Postgres reads only that day's partitions (checked with EXPLAIN); without one it reads every partition. Then open the message in the console by its id (/messages/<id>). The tables are described in the database schema.

  • Know what that bypasses: a payload read through Ductus is recorded in its audit log before it's shown; a query in psql isn't. Give such logins only to people your privacy officer would let read every message, and prefer selecting ids and keys (as above) over payload.

Why not yet, and what's planned: nuance.

Who can see what

  • Everyone signed in sees messages' metadata (ids, statuses, errors, keys).
  • Payloads need the payloads permission, and every read is logged before it's shown, once per reader per message per 10 minutes (nuance). The message's page lists who viewed the payload.
  • Errors can hold data: a receiver's AR text or a quarantine reason may quote a field. Write quarantine reasons that name the field and the problem, not the patient.