Quickstart
By the end of this guide you will have fired a trigger and watched palumb turn it into a real email — using managed palumb. You write the workflow behind a signed webhook (the Bridge); palumb runs the control plane, the durable engine, and the delivery. It exercises every part of the architecture: a channel, a subscriber, a workflow, and a real send.
Prerequisites
Section titled “Prerequisites”- A palumb account — sign up (email + password, then verify your email) and
log in to the dashboard. That’s where you create your API key (shown
once, at creation) and where you’ll later manage channels, subscribers, and
billing. Your base URL is the managed endpoint (e.g.
https://api.palumb.com). - Node.js 22+ — to run your Bridge.
Set your credentials once; every step below uses them:
export PALUMB_BASE_URL="https://api.palumb.com"export PALUMB_API_KEY="pk_live_…"1. Configure a channel
Section titled “1. Configure a channel”A channel is a concrete provider. Enable email-smtp
with your transactional email provider’s SMTP credentials (Scaleway TEM,
Mailgun, Postmark, …). palumb stores them encrypted and never returns them —
they never live in your app code:
curl -sS "$PALUMB_BASE_URL/v1/channels" \ -H "Authorization: Bearer $PALUMB_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "channelKey": "email-smtp", "config": { "host": "smtp.your-provider.com", "port": 587, "secure": false, "username": "your-smtp-user", "password": "your-smtp-secret", "fromEmail": "noreply@yourdomain.com", "fromName": "Acme" } }'2. Add a subscriber
Section titled “2. Add a subscriber”A subscriber is a recipient, addressed by your own
external_id. Give cust_1 an email contact:
curl -sS -X PUT "$PALUMB_BASE_URL/v1/subscribers/cust_1" \ -H "Authorization: Bearer $PALUMB_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "contacts": { "email": ["cust1@example.com"] } }'3. Write and deploy your Bridge
Section titled “3. Write and deploy your Bridge”Your workflows are plain TypeScript built with
@palumb/sdk, exposed as one signed HTTP endpoint.
This workflow emails an order confirmation:
import express, { type Request, type Response } from 'express';import { workflow, BridgeClient, serveNest } from '@palumb/sdk/bridge';
const orderCreated = workflow('orderCreated', async ({ payload, subscriber, step }) => { const orderId = String(payload.orderId ?? 'unknown'); await step.send('confirm', 'email', () => ({ subject: `Order ${orderId} received`, body: `<p>Thanks, ${subscriber?.subscriberId}! We got order <b>${orderId}</b>.</p>`, }));});
const bridge = new BridgeClient().register([orderCreated]);const handle = serveNest(bridge, { signingKey: process.env.PALUMB_SIGNING_KEY! });
const app = express();// Capture the raw body so the HMAC verifies against the exact bytes palumb signed.app.use(express.json({ verify: (req, _res, buf) => { (req as Request & { rawBody?: string }).rawBody = buf.toString('utf8'); },}));app.all('/api/palumb', (req: Request, res: Response) => handle(req as any, res as any));app.listen(9080, () => console.log('bridge on http://localhost:9080/api/palumb'));Deploy this anywhere palumb can reach over HTTPS. See Writing a workflow for the line-by-line walkthrough.
4. Connect your Bridge to palumb
Section titled “4. Connect your Bridge to palumb”Tell palumb where your Bridge lives. palumb generates a signing secret, returns it once, and uses it to sign every call to your webhook:
curl -sS -X PUT "$PALUMB_BASE_URL/v1/bridge" \ -H "Authorization: Bearer $PALUMB_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "webhookUrl": "https://your-app.com/api/palumb" }'# → { "webhookUrl": "...", "signingSecret": "whsec_…" } (shown ONCE)Set that secret as PALUMB_SIGNING_KEY in your Bridge’s environment and restart
it, so serveNest can verify palumb’s signature.
5. Fire a trigger
Section titled “5. Fire a trigger”Now play your app — fire orderCreated for cust_1:
curl -sS "$PALUMB_BASE_URL/v1/events" \ -H "Authorization: Bearer $PALUMB_API_KEY" \ -H 'Content-Type: application/json' \ -d '{ "workflowId": "orderCreated", "to": { "subscriberId": "cust_1" }, "payload": { "orderId": "O-1" } }'The call returns 202 with the notification id.
6. Watch the email arrive
Section titled “6. Watch the email arrive”Check the inbox of cust1@example.com — subject Order O-1 received.
POST /v1/events→ palumb started a run on its engine forcust_1.- The engine called your Bridge (HMAC-signed) for the next step — your
orderCreatedworkflow returned asend. - palumb resolved your
email-smtpchannel, decrypted it, and made the real send through your provider — recording the attempt idempotently.
That is the entire loop, durably — and you operated none of it. If the engine had restarted mid-run, the run’s state would have survived. See Durability & Restate.
- Writing a workflow — build your own workflow.
- Sending messages —
step.sendin depth. - Digest — batch many items into one message.
- Self-hosting — run palumb yourself instead.