Production checklist
The dozen decisions between "works on my machine" and a receiver-and-sender you can page someone about.
Everything on this page is covered in depth elsewhere — this is the pass you make once, before real traffic, with links into the details.
Storage & schema
- Swap
InMemoryStorage()for a database adapter. In-memory loses the outbox on restart and can't coordinate multiple instances. - Set
autoMigrate: falseand runpostel migratein your deploy step, so app boot never runs DDL and N instances never race to migrate. The version handshake turns a forgotten migration into a loud boot failure. - Treat the database as your secret boundary. Until KMS lands, signing secrets are stored plaintext in
endpoint_secrets.material— same posture as any secret store without a KMS in front.
Lifecycle
- Call
postel.start()once at boot —send()only queues; without a worker nothing delivers, silently. On serverless hosts, usedrain()on a cron instead. - Call
await postel.stop()on graceful shutdown (SIGTERM handler), so in-flight attempts finish and leases release cleanly. - Wire
postel.health()into your readiness probe — it reportsok: falseon storage failure or when a configuredobservability.healththreshold (maxOutboxDepth,maxOldestPendingAge) is breached. Also served asGET /healthby the admin router.
Delivery policy
- Choose a retry schedule deliberately. The default spans 5s→3d over 9 attempts; a strict SLA customer may deserve a per-endpoint override.
- Leave the circuit breaker and auto-disable on — one dead endpoint must not saturate the pool. Decide who re-enables a disabled endpoint (a human via the admin API, or a reconciliation job).
- Have a dead-letter policy: alert on the
dead-letterevent (the alerting recipe), and know that recovery is onereplay()call.
Security
- Receivers: no
Noop()outside a trusted boundary, and keep dedup on for non-idempotent handlers — the delivery guarantees page states exactly what at-least-once means for you. - Senders: plan key rotation before you need it —
rotateSecret({ keepPreviousFor })gives receivers an overlap window; asymmetric (Ed25519V1a+ JWKS) means receivers never hold your signing material. - Lock down the admin router — it's default-deny (
authorizerequired), but it's still your control plane: mount it behind your ops auth, never on a public path. - SSRF defense stays on. Endpoint URLs are validated at create time and re-checked at dispatch (
SsrfBlocked); only setallowHttpfor endpoints you control.
Observability
- Forward
observability.loggerinto your log pipeline — every delivery and circuit event, withtrace_idcorrelation when OTel is active. - Scrape
postel.metrics()(Prometheus-named counters/histograms/gauges) and alert onwebhook_dead_letter_totalandwebhook_outbox_depthat minimum — the observability page has the export recipe.
Multi-instance notes
Multiple app instances each running postel.start() is the supported topology — workers coordinate through row leases (FOR UPDATE SKIP LOCKED), no extra configuration. SQLite is the exception: single-writer semantics make it a one-process deployment.
Custom adapters
Implement the Storage interface for any backend, with zero-DB helpers and a conformance battery to prove it.
Migrations & postel migrate
How Postel's schema reaches your database — autoMigrate for development, the postel migrate CLI for deploy pipelines, and the version handshake in between.