Skip to content
postmaster logoPostmasterAsync
Typed durable job outbox with leased processing, retries, and dead-letter recovery for browser applications.
Version
v26.10.5
Size
3.3 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' });
postmaster.start();
FeaturePostmasterAd hoc outboxFamiliar
Bundle size3.3 KBApplication-defined3.9 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' });
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, with optional delayed eligibility via availableAt.
  • 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 ​