Skip to main content
Introducing packages.sweber.dev
Documentation menuWorkers and serverless

Workers and serverless

Run deliveries in a long-running process, a cron job, or a queue consumer.

send only stores the message. Deliveries happen when something calls process().

Long-running server

const worker = vector.start({ intervalMs: 1000, concurrency: 10 });

process.on("SIGTERM", async () => {
  await worker.stop(); // finishes the current batch
  process.exit(0);
});

The worker picks up to limit (default 50) due deliveries, sends them with up to concurrency requests in parallel, and immediately looks again if the batch was full; otherwise it waits intervalMs. With the PostgreSQL store you can run as many workers as you like.

Serverless and cron

On Vercel, Netlify or AWS Lambda there is no long-running process. Call process() from a scheduled function instead:

// app/api/cron/webhooks/route.ts, scheduled every minute
export async function GET() {
  const result = await vector.process({ limit: 100 });
  return Response.json(result); // { claimed, succeeded, retrying, failed, cancelled }
}

For the first attempt not to wait for the next cron run, send with deliverNow: true:

await vector.send({ tenant, eventType: "invoice.paid", payload, deliverNow: true });

Only the first attempt runs inline; retries are picked up by the scheduled process().

Queues

If you already have a queue (BullMQ, SQS, Inngest, Trigger.dev), call vector.process() from a repeating job. Each claimed delivery is locked for timeoutMs plus one minute, so a crashed worker's deliveries are picked up again after that.

In tests

Keep it synchronous: use the default memory store, a fake fetch and a fixed now, then call process() yourself.

const vector = createVector({
  fetch: async () => new Response(null, { status: 204 }),
  now: () => new Date("2026-10-05T12:00:00Z"),
  urlPolicy: { allowPrivateNetworks: true },
});