Set message retention
Stored messages are patient data at rest, so Pathways deletes them once they are done and their retention period has passed. A message is done when it is delivered, filtered or cleared; its period runs from its last activity. The default is 30 days.
Failed messages are never deleted by retention: they wait until you replay or clear them. Neither is a message with a delivery still pending.
Set the period
For the whole project, and for one channel:
encryption:
keys:
- id: dev
file: .pathways/dev.key
current: dev
defaults:
retention: 30d
retentionInterval: 1h
retention: 7d
source:
file: { dir: ../../data/adt-to-lab/in }
transform: ./transform.ts
destinations:
- file: { dir: ../../data/adt-to-lab/out }
A channel that has stored messages but is no longer in the project keeps the project's period.
When it runs
A running engine (pathways start) runs retention once it has made its first pass, then every
retentionInterval (1 hour by default). Each run deletes in batches, so the engine is never held up
long, and writes one entry to the audit log with what it deleted. pathways start --once does not run
retention.
To run it yourself, or to see first what it would do:
npx pathways retention --dry-run
npx pathways retention
Retention dry run: 0 messages and 0 deliveries would be deleted. Nothing was deleted.
adt-to-lab: 0 messages, 0 deliveries (done, and last active before 2026-10-11T07:40:48.263Z)
A dry run deletes nothing and is not audited.
Drop content after delivery
Retention deletes a message some time after it is done. To keep as little patient data as you can, drop a message's content as soon as it is delivered instead:
encryption:
keys:
- id: dev
file: .pathways/dev.key
current: dev
storage:
content: dropAfterDelivery
storage.content is keep by default. A channel overrides the project's:
storage:
content: keep
source:
file: { dir: ../../data/adt-to-lab/in }
transform: ./transform.ts
destinations:
- file: { dir: ../../data/adt-to-lab/out }
With dropAfterDelivery, the transaction that records a message's last delivery as delivered also
deletes the message's body as it was received and the body of every one of its deliveries. Only the
deliveries of the message's current generation count: one that failed for good before a replay or
resend does not hold the drop back. While any delivery of the current generation has failed or waits
to be tried again, every body is kept, so you can still replay or resend it. A filtered message, and
one whose transform failed, keeps its content until retention deletes it. So does a message with a
delivery that was cleared or sent as a dry run: neither is delivered, so the drop never comes.
What stays is the message's metadata: its id, channel, times, status and its deliveries' statuses,
attempts and times, its size, the possible-duplicate flag, and a SHA-256 hash of the body as it was
received, which the engine works out as it stores the message. pathways messages show prints the
hash and when the content was dropped. Each delivery's last error text goes with the bodies, since
encryption treats it as content too; its attempts count stays.
The message's ctx.meta goes with the bodies too, and so do the tags of every index over it
(search messages by metadata), in the same transaction: they
hold identifiers such as a medical record number. A message whose content is dropped can no longer
be found with pathways messages --where.
The hash is plain SHA-256 with no key, and it is stored unencrypted, even with encryption at rest. Anyone who can read the database and can guess a message's exact text, which for a short or predictable message may not be hard, can hash the guess and confirm whether that message passed through. Protect the hash as you would the content.
Once the content is dropped:
pathways replayandpathways replay --resendrefuse the message, changing nothing: there is nothing to send again.pathways messages show --contenthas no content to print.pathways rekeyskips it: it holds nothing encrypted.- Retention leaves it: its metadata is kept, and no retention period deletes it.
The engine logs content-dropped with the channel, the message and the delivery. A message
received under keep and delivered after you switch to dropAfterDelivery is dropped too, without
a hash. A custom store needs markDeliveredAndDropContent for this; pathways start refuses a
channel that asks for it on a store without it. What each database still holds after the delete is
on Store messages in a database server.
What retention does not reach
- Files a file source keeps with
keepProcessed: true, in itsprocessed/folder. - Files a file destination wrote.
- The audit log, which is never deleted by Pathways.
- Copies of the database in backups, and on a database server, earlier row versions in its logs.
On SQLite, a deleted message's bytes are overwritten with zeros in the file; on a server they can stay for a while, as what a database keeps after a delete says. The period your policy needs, and what happens to backups and output folders, are yours to decide.