postal ships as ESM and CJS with full TypeScript declarations. No peer dependencies.
import { getChannel } from "postal";
// Get (or create) a channel
const orders = getChannel("orders");
// Subscribe to a topic
const unsub = orders.subscribe("item.placed", envelope => {
console.log("New order:", envelope.payload);
console.log("Topic:", envelope.topic);
console.log("Timestamp:", envelope.timestamp);
});
// Publish a message
orders.publish("item.placed", { sku: "WIDGET-42", qty: 3 });
// Unsubscribe when done
unsub();
getChannel is a singleton factory — calling it with the same name always returns the same channel instance. Subscribers receive the full Envelope, not just the payload.
const ch = getChannel("events");
// * matches exactly one segment
ch.subscribe("user.*", env => {
// Matches: user.created, user.deleted
// Does NOT match: user.profile.updated
});
// # matches zero or more segments
ch.subscribe("user.#", env => {
// Matches: user, user.created, user.profile.updated
});
See Concepts for the full wildcard matching rules.
postal supports compile-time payload inference via module augmentation. Declare your channel’s topic-to-payload map once in a .d.ts or any TypeScript file, and every getChannel call site picks it up automatically — no generics required:
declare module "postal" {
interface ChannelRegistry {
orders: {
"item.placed": { sku: string; qty: number };
"item.cancelled": { sku: string; reason: string };
};
}
}
// getChannel("orders") now infers the full topic map automatically
const orders = getChannel("orders");
orders.publish("item.placed", { sku: "WIDGET-42", qty: 3 }); // Typed!
postal includes correlation-based RPC. Define RPC topics with { request, response } payloads:
declare module "postal" {
interface ChannelRegistry {
pricing: {
"quote.request": {
request: { sku: string; qty: number };
response: { total: number; currency: string };
};
};
}
}
const pricing = getChannel("pricing");
// Register a handler (one per topic per channel)
const unhandle = pricing.handle("quote.request", envelope => {
const { sku, qty } = envelope.payload;
return { total: qty * 9.99, currency: "USD" };
});
// Send a request — returns a Promise
const quote = await pricing.request("quote.request", {
sku: "WIDGET-42",
qty: 5,
});
console.log(quote); // { total: 49.95, currency: "USD" }