How it works
palumb has two halves. Understanding the split is most of understanding palumb.
The platform (palumb runs this)
Section titled “The platform (palumb runs this)”palumb runs the whole durable platform for you. Two services:
palumb-core — the managed multitenant control plane. It is the only part
that holds your state and secrets, and it does exactly four jobs:
- Authenticates you. Every request from your backend carries a tenant API key — the key is the machine identity. (Humans manage those keys, channels, and billing by signing in to the dashboard; the machine API itself stays key-scoped.) See Tenants & API keys.
- Knows your audience. Subscribers, topics, and subscriptions live here, so a trigger can target “one user” or “everyone subscribed to this topic”. See Audience.
- Holds your channels. A channel is a concrete provider (e.g. an SMTP relay). You attach your credentials once; palumb stores them encrypted at rest and never returns them. See Channels.
- Receives triggers and makes deliveries. It is the front door
(
POST /v1/events) and the back door that actually talks to providers.
palumb-engine — the durable runtime, built on Restate.
It runs a generic executor that drives your workflows step by step and persists
their state and timers. You never operate it, and no Restate runs on your side.
Your workflow code (you run this)
Section titled “Your workflow code (you run this)”Your notification logic is plain TypeScript built with the
@palumb/sdk Bridge SDK, exposed as one signed HTTP
endpoint — the Bridge. You host nothing durable: just that webhook. Two ideas
cover almost everything:
workflow(id, fn)— declares a workflow.idis what a trigger names, andfnis plain TypeScript that awaits durable steps. See Triggers and Writing a workflow.step.*— the durable steps inside a workflow:step.run(arbitrary code),step.send(deliver a message),step.delay(durable wait), andstep.waitForEvent(suspend until an external signal). palumb’s engine records each completed step and never re-runs it. See Sending messages.
palumb’s engine calls your Bridge to ask “what’s the next step?”, runs it durably, and repeats until the workflow finishes — so the durability lives in palumb while the logic lives in your code.
On top of those, palumb ships building blocks — durable patterns you would otherwise hand-roll. The first is Digest: accumulate many items for a subscriber and flush them as one message on a window or a schedule.
The full loop
Section titled “The full loop”-
Your app fires a trigger.
POST /v1/eventswith{ workflowId, to, payload }, authenticated by your API key. -
The control plane resolves recipients.
tois either one subscriber or a topic fan-out; palumb expands it to a deduplicated set of subscribers. -
The engine starts a run — once per subscriber.
palumb-coresubmits a run of the generic executor onpalumb-engine, carrying your Bridge binding. Each run targets exactly one subscriber. -
The engine drives your Bridge, durably. It calls your webhook (HMAC-signed) to run the next step, records the result, and loops. Your workflow can branch, wait days for an event, or route into a digest — state and timers survive restarts.
-
Your workflow declares a send.
step.send(id, "email", () => content)names a channel kind and renders already-formatted content. The engine turns that into an internal delivery call. -
The control plane delivers. It resolves your enabled channel of that kind, decrypts its credentials, looks up the subscriber’s address, calls the provider, and records the attempt — idempotently.
The integration is two HTTP edges: your app → /v1/events (a trigger in) and
palumb → your signed Bridge (palumb driving your workflow). That split keeps
your code free of provider secrets and keeps palumb free of your business logic.
- Architecture — the same loop, with the data model and failure behavior.
- Quickstart — run all six steps end to end.