MLLP Server

HL7 MLLP server thatvalidates before routing.

Receives HL7 v2 messages over MLLP, validates them with CEL expressions, routes to downstream systems, and persists everything to an embedded outbox. One binary. No JVM. No message broker. Deploys to Kubernetes or a single VM.

config.yaml
connectors:
  - name: ehr-ingest
    type: http
    url: https://ehr.example.com/hl7
    filter: msh.msg_type == "ADT"

  - name: lab-archive
    type: postgres
    filter: msh.msg_type == "ORU"

validation:
  rules:
    - name: require-patient-id
      expression: pid.id != ""

What the server handles for you

So your integration code only deals with business logic.

CEL Validation

Compiled expression rules

Write validation rules once in CEL. They compile to bytecode at startup—no interpreter overhead at message time. Field access, list iteration, optional chaining.

  • <100 µs per message
  • Cost-limited expressions (max 1000)
  • MSH, PID, PV1, OBX, obx_list variables
TLS / mTLS

Mutual TLS, no restarts

Configure TLS 1.2+ with optional client certificate verification. Rotate certificates by sending SIGHUP—the server reloads without dropping existing connections.

  • TLS_CERT_FILE, TLS_KEY_FILE, TLS_CLIENT_CA
  • Hot-reload on SIGHUP
  • TLS 1.2 minimum, 1.3 opt-in
Connectors

CEL-routed pluggable outputs

Each connector declares a CEL filter expression. Matching messages are forwarded; misses are silently skipped. Failed deliveries retry with backoff and land in a dead-letter queue.

  • Per-connector CEL filter
  • Retry with exponential backoff
  • Dead-letter queue (DLQ)
Observability

OpenTelemetry out of the box

Metrics exported via OTLP. Ring buffer captures recent log lines for in-process inspection. Every message produces an audit record: who sent what, when, and with what result.

  • OTLP metrics (counters, histograms)
  • Ring buffer log capture
  • Immutable audit trail
Persistence

Embedded outbox, atomic fanout

bbolt stores every accepted message before any connector sees it. Fanout to multiple connectors is atomic—either all see the message or none do. FIFO delivery is guaranteed per connector.

  • bbolt embedded key-value store
  • Transactional outbox pattern
  • Guaranteed FIFO per connector
12-Factor

Single binary, env-driven

No JVM, no runtime, no config file required. Every option is an environment variable with a sensible default. Drop it anywhere and it runs.

LISTEN_ADDR=:2575
IDLE_TIMEOUT=30s
MAX_CONNECTIONS=0

Up in three steps.

From zero to receiving HL7 messages in under a minute.

1

Download

shell
# Extract the archive for your platform
tar -xzf mllp-server_1.0.1_linux_amd64.tar.gz

# Verify the checksum
sha256sum --check checksums.txt
2

Run

shell
# Default config — listens on :2575
./mllp-server

# Override any option via env
LISTEN_ADDR=:3000 ./mllp-server
3

Send a message

shell
# Real MLLP frame: VT + payload + FS + CR
printf '\x0bMSH|^~\&|A|B|C|D|20240101||ORU^R01|123|P|2.3\r\x1c\x0d' \
  | nc localhost 2575

Message pipeline

Every inbound message follows the same deterministic path.

Accept TCP / TLS
Frame MLLP 0x0B…0x1C0x0D
Parse MSH, PID, OBX
Validate CEL rules
Route Connector filter
Persist bbolt outbox
Process Connector fanout
ACK AA / AR / AE

On validation failure

Returns AR — Application Reject. Connection stays open.

On internal error

Returns AE — Application Error. Message lands in DLQ.

On success

Returns AA — Application Accept. Persisted before ACK.

Ships with Kustomize overlays for dev, staging, and prod.

The repository ships Kustomize overlays for dev, staging, and prod environments. Each overlay patches resource limits, replica counts, and TLS secrets—while the base manifest stays canonical.

  • Graceful shutdown with configurable drain timeout (default 30 s)
  • SIGTERM-aware: drains in-flight messages before exit
  • TLS secrets mounted from Kubernetes Secrets; SIGHUP for live rotation
  • OpenTelemetry collector sidecar pattern supported
shell
# Render and apply the prod overlay
kustomize build deploy/overlays/prod \
  | kubectl apply -f -

# Or with native kubectl kustomize
kubectl apply -k deploy/overlays/prod

# Available overlays:
# deploy/overlays/dev
# deploy/overlays/staging
# deploy/overlays/prod
# deploy/overlays/persistent

Ready to deploy?

Read the getting-started guide or browse the full configuration reference.