Storage

Drizzle

@postel/drizzle — run Postel's storage through your existing Drizzle instance (Postgres, MySQL, or SQLite).

View as Markdown

@postel/drizzle is an ORM adapter: hand Postel the Drizzle db you already use and it issues its storage queries through it, so outbox writes share your connection and transactions.

pnpm add @postel/drizzle drizzle-orm
npm install @postel/drizzle drizzle-orm
yarn add @postel/drizzle drizzle-orm
bun add @postel/drizzle drizzle-orm

Plus your database driver — pg, mysql2, or better-sqlite3.

lib/postel.ts
import { drizzle } from "drizzle-orm/node-postgres";
import { Postel } from "@postel/core";
import { DrizzleStorage } from "@postel/drizzle";
import { config } from "./config.js";

const db = drizzle(config.databaseUrl);

export const postel = Postel({
  outbound: {
    storage: DrizzleStorage({ db, dialect: "postgres" }),
  },
});

Options

OptionNotes
dbThe Drizzle database you already built — any driver (drizzle-orm/node-postgres, drizzle-orm/better-sqlite3, …). Postel issues its queries through it.
dialect"postgres", "mysql", or "sqlite" — selects the reservation strategy (FOR UPDATE SKIP LOCKED on Postgres/MySQL, ordered on SQLite), the capability flags, and the column codecs.
autoMigrateRun migrations on first use (default true).
clockInject a clock for deterministic time in tests.

drizzle-orm is a peer dependency. Postel runs through Drizzle's sql tag, so no Postel-specific table definitions are required in your schema. The dialect names a SQL family, so wire-compatible engines (MariaDB, PlanetScale, libSQL/Turso, …) work via the matching dialect — see compatible databases.

Shared transactions

Pass a Drizzle transaction to send() so the outbox insert commits atomically with your business writes:

await db.transaction(async (tx) => {
  await tx.insert(orders).values({ /* ... */ });
  await postel.outbound.send({ type: "order.created", data: { /* ... */ } }, { tx });
});

Migrations

autoMigrate (default true) runs the canonical migrations through your connection, version-gated and idempotent — or run your own tooling against that schema.

The /schema export

If you'd rather have Postel's tables live in your own Drizzle schema file — so drizzle-kit generate / push / studio see them — import the fragment instead of relying on autoMigrate:

db/schema.ts
export { pgTenants, pgEndpoints, pgMessages, pgAttempts } from "@postel/drizzle/schema";

The export is namespaced by dialect prefix (pg*, mysql*, sqlite*) for all seven canonical tables (tenants, endpoints, endpointSecrets, messages, attempts, endpointStateTransitions, receivedMessages). Merge the ones matching your dialect into your schema and run migrations with your own tooling; pass autoMigrate: false to DrizzleStorage so Postel doesn't also try.

On this page