MessageFoundry routes, transforms, and validates HL7 v2 messages between systems — with the routing logic authored as plain, version-controlled Python instead of a proprietary low-code canvas.
from messagefoundry import MLLP, File, Send, inbound, outbound, router, handler # Connections — named endpoints, wired together by name inbound("IB_Test_ADT", MLLP(port=2575), router="adt_router") outbound("FILE_Test_ADT", File(directory="./out/adt", filename="{MSH-10}.hl7")) @router("adt_router") def route(msg): if msg["MSH-9.1"] != "ADT": return [] # not ADT → UNROUTED return ["archive"] @handler("archive") def archive(msg): if msg["MSH-9.2"] not in ("A01", "A04", "A08"): return None # filtered out (FILTERED) return Send("FILE_Test_ADT", msg)
A focused, reliable alternative to heavyweight engines for simple point-to-point interfaces — ADT, ORU, SIU, DFT. Message flow is a graph wired by name: an inbound Connection names a Router, which forwards to one or more Handlers, which send to outbound Connections — all backed by durable queuing, retries, and replay.
Connections, Routers, and Handlers are plain Python modules — diffable, reviewable, and version-controlled. The database holds runtime state and messages only, never configuration.
A transactional inbox/outbox on SQLite (WAL) gives at-least-once delivery, retries, replay, and dead-lettering — without standing up a separate message broker.
Authentication, role-based access control, a user-attributed audit log of message views and replays, and AES-256-GCM encryption-at-rest for message bodies are built in.
Fast field peek with python-hl7 on the routing hot path; opt-in, version-aware strict validation with hl7apy where you need it. Real-world HL7 is non-conformant — the engine expects that.
Every received message is persisted with a disposition before the ACK — PROCESSED, UNROUTED, FILTERED, or ERROR — so inbound counts always reflect true volume.
A PySide6 admin console (dashboard, message browser, HL7 parse-tree viewer, replay) plus a VS Code extension with a route wizard, validate-on-save, and a dry-run Test Bench.
There's no monolithic "channel" object bundling everything together. You wire named building blocks — and that wiring is the system.
Receives messages (MLLP, file). Names a Router. Every receipt is counted and logged.
Sees every message and decides where it goes — forward to one or more Handlers, or filter.
Filters, then transforms, then returns Sends addressed to outbound Connections.
Drains its outbox independently, with retries. A slow peer never blocks its siblings.
At-least-once by construction. The inbound connection is ACKed only after the message and its per-outbound outbox rows are durably committed — so a crash can never lose an accepted message.
Clone the repo, wire a route in a few lines of Python, and send it a test message over MLLP.