Skip to content
postmaster logoPostmasterAsync
Typed durable job outbox with leased processing, retries, and dead-letter recovery for browser applications.
Version
v1.0.0
Size
3.0 KB gzip
BrowserNode ≥22
createPostmasterdefineJobscreateIndexedDbPostmasterStorecreateMemoryPostmasterStorePostmasterError View all 7 exports

Why Postmaster?

Application jobs that touch a remote service — posting a form, syncing state, sending analytics — must survive page reloads, resume later, retry according to an explicit policy, and retain terminal failures for recovery. Postmaster coordinates that delivery with typed job definitions, leased processing, and a dead-letter queue, all backed by IndexedDB.

ts
// Before
async function createTodo(payload: { id: string; title: string }) {
  // Lost on reload. No retry. No recovery. Silent failure.
  await fetch('/api/todos', { method: 'POST', body: JSON.stringify(payload) });
}

// After
import { createPostmaster, defineJobs } from '@vielzeug/postmaster';
import { createIndexedDbPostmasterStore } from '@vielzeug/postmaster/indexeddb';
import { s } from '@vielzeug/spell';

const jobs = defineJobs({
  createTodo: {
    version: 1,
    validate: s.object({ id: s.string(), title: s.string() }),
    key: (p) => p.id,
    execute: async (payload, { key, signal }) => {
      await fetch('/api/todos', {
        method: 'POST',
        body: JSON.stringify(payload),
        headers: { 'Idempotency-Key': key },
        signal,
      });
    },
  },
});

const store = createIndexedDbPostmasterStore({ name: 'my-app-outbox' });
const postmaster = createPostmaster({ jobs, store });

await postmaster.enqueue('createTodo', { id: crypto.randomUUID(), title: 'Buy milk' });
await postmaster.start();
FeaturePostmasterAd hoc outboxFamiliar
Bundle size3.0 KBApplication-defined3.8 KB
Zero dependencies
Survives page reload
Leased cross-tab processing
Dead-letter recovery
Typed job payloads

Use Postmaster when application jobs must survive reloads, retry explicitly, and remain recoverable after terminal failure.

Consider Familiar when jobs are CPU-bound, in-memory only, and never need to survive a page reload.

Installation

sh
pnpm add @vielzeug/postmaster
sh
npm install @vielzeug/postmaster
sh
yarn add @vielzeug/postmaster

For browser persistence, also install @vielzeug/vault (a workspace peer of the IndexedDB adapter):

sh
pnpm add @vielzeug/postmaster @vielzeug/vault
sh
npm install @vielzeug/postmaster @vielzeug/vault
sh
yarn add @vielzeug/postmaster @vielzeug/vault

Quick Start

Define typed jobs, create a durable store, enqueue work, and start the processor. Dispose both the processor and the store when the page lifetime ends.

ts
import { createPostmaster, defineJobs } from '@vielzeug/postmaster';
import { createIndexedDbPostmasterStore } from '@vielzeug/postmaster/indexeddb';

const jobs = defineJobs({
  createTodo: {
    version: 1,
    validate: (v: unknown) => v as { id: string; title: string },
    key: (p) => p.id,
    execute: async (payload, { key, signal }) => {
      await fetch('/api/todos', {
        method: 'POST',
        body: JSON.stringify(payload),
        headers: { 'Idempotency-Key': key },
        signal,
      });
    },
    retry: { maxAttempts: 5, shouldRetry: () => true },
  },
});

const store = createIndexedDbPostmasterStore({ name: 'my-app-outbox' });
const postmaster = createPostmaster({ jobs, store });

await postmaster.enqueue('createTodo', { id: crypto.randomUUID(), title: 'Buy milk' });
await postmaster.start();

// On page unload:
await postmaster.dispose();
await store.dispose();

Features

  • defineJobs() — Typed job registry with payload inference and validation.
  • createPostmaster() — Processor with leased claims, heartbeat renewal, and crash recovery.
  • enqueue() — Persist a job and wake the processor.
  • flush() — Process every available job until the queue is empty.
  • retry() / remove() — Recover or discard dead-letter jobs.
  • tap() — Typed runtime events for enqueued, started, completed, retry-scheduled, dead-lettered, removed, lease-lost, and processor-error.
  • createIndexedDbPostmasterStore() — Durable browser store backed by Vault IndexedDB.
  • createMemoryPostmasterStore() — Deterministic in-memory store for tests.

See Also