Strategies
The retry, rate-limit, worker, signing, and KMS strategy factories that configure OutboundConfig and EndpointCreateOptions.
Every factory on this page is exported from @postel/core and returns a plain, serializable strategy value — set on OutboundConfig for an org-wide default or on EndpointCreateOptions to override it per endpoint. See Retries and Signing for the narrative version of retry/signing behavior.
Retry
type RetryStrategy =
| { readonly kind: "exponential"; readonly schedule: ReadonlyArray<DurationMs>; readonly jitter: number; readonly maxAttempts: number }
| { readonly kind: "linear"; readonly step: DurationMs; readonly maxAttempts: number }
| { readonly kind: "custom"; readonly compute: (attempt: number) => DurationMs; readonly maxAttempts: number };
function ExponentialBackoff(options?: ExponentialBackoffOptions): RetryStrategy;
function LinearBackoff(options: LinearBackoffOptions): RetryStrategy;
function Custom(options: CustomRetryOptions): RetryStrategy;
interface ExponentialBackoffOptions {
readonly schedule?: ReadonlyArray<DurationMs>; // default: 5s, 5m, 30m, 2h, 5h, 10h, 1d, 2d, 3d
readonly jitter?: number; // default 0.2
readonly maxAttempts?: number; // default: schedule.length
}
interface LinearBackoffOptions {
readonly step: DurationMs;
readonly maxAttempts: number;
}
interface CustomRetryOptions {
readonly compute: (attempt: number) => DurationMs;
readonly maxAttempts: number;
}ExponentialBackoff() with no arguments is the default retryPolicy when OutboundConfig.retryPolicy is unset. A Custom strategy's compute function is code-side — it's excluded from the serializable Endpoint.retryPolicy read shape (SerializableRetryStrategy reads back null for it).
Rate limit
type RateLimitStrategy = { readonly kind: "fixed"; readonly perSecond: number };
function FixedRate(options: FixedRateOptions): RateLimitStrategy;
interface FixedRateOptions {
readonly perSecond: number;
}FixedRate is the only kind today. Set per-tenant via postel.outbound.tenants.setRateLimit(tenantId, { perSecond }) — see Tenant.rateLimit.
Workers
type WorkerStrategy =
| { readonly kind: "in-process"; readonly concurrency: number }
| { readonly kind: "bullmq"; readonly queue: unknown }
| { readonly kind: "pg-boss"; readonly boss: unknown }
| { readonly kind: "external"; readonly adapter: unknown };
function InProcess(options?: InProcessOptions): WorkerStrategy;
interface InProcessOptions {
readonly concurrency?: number; // default 4
}
/** @deprecated Not implemented — throws NotImplementedError at Postel(...) construction. Use InProcess(). */
function BullMQ(queue: unknown): WorkerStrategy;
/** @deprecated Not implemented — throws NotImplementedError at Postel(...) construction. Use InProcess(). */
function PgBoss(boss: unknown): WorkerStrategy;
/** @deprecated Not implemented — throws NotImplementedError at Postel(...) construction. Use InProcess(). */
function External(adapter: unknown): WorkerStrategy;InProcess() (the default) is the only worker strategy with a runtime today. BullMQ/PgBoss/External are typed config slots — passing one to OutboundConfig.workers throws NotImplementedError immediately when you call Postel(...), not on first send. They exist so the config shape and cross-port contract are already settled for when those adapters ship.
Signing
type SigningStrategy =
| { readonly kind: "hmac-v1"; readonly alsoSign?: ReadonlyArray<SigningStrategy> }
| { readonly kind: "ed25519-v1a"; readonly alsoSign?: ReadonlyArray<SigningStrategy> };
function HmacV1(options?: SigningOptions): SigningStrategy;
function Ed25519V1a(options?: SigningOptions): SigningStrategy;
interface SigningOptions {
readonly alsoSign?: ReadonlyArray<SigningStrategy>;
}alsoSign layers additional signatures onto the same outgoing request (e.g. sign with both HmacV1() and Ed25519V1a() during a rotation window) — the receiver's verify()/Verifier tries each signature tuple in the webhook-signature header until one matches.
KMS
type KmsStrategy =
| { readonly kind: "aws-kms"; readonly keyId: string }
| { readonly kind: "gcp-kms"; readonly keyName: string }
| { readonly kind: "vault"; readonly transitPath: string; readonly keyName: string }
| { readonly kind: "plaintext"; readonly allowInProduction: boolean };
function PlaintextKms(options?: PlaintextKmsOptions): KmsStrategy;
interface PlaintextKmsOptions {
readonly allowInProduction?: boolean; // default false
}
/** @deprecated Not implemented — throws NotImplementedError at Postel(...) construction. Use PlaintextKms(). */
function AwsKms(options: AwsKmsOptions): KmsStrategy;
/** @deprecated Not implemented — throws NotImplementedError at Postel(...) construction. Use PlaintextKms(). */
function GcpKms(options: GcpKmsOptions): KmsStrategy;
/** @deprecated Not implemented — throws NotImplementedError at Postel(...) construction. Use PlaintextKms(). */
function Vault(options: VaultOptions): KmsStrategy;
interface AwsKmsOptions { readonly keyId: string; }
interface GcpKmsOptions { readonly keyName: string; }
interface VaultOptions { readonly transitPath: string; readonly keyName: string; }Same pattern as workers: PlaintextKms() (the default) is the only KMS strategy with a runtime — endpoint secrets are stored as-is. AwsKms/GcpKms/Vault are typed slots for the envelope-encryption work that hasn't shipped; configuring one throws NotImplementedError at construction.
Verifiers
Inbound signature verification (Secret, PublicKey, Keyset, Noop, and the named provider verifiers Stripe/GitHub/Shopify/Twilio/Slack) lives on the Inbound API page alongside the rest of InboundSource — they're strategy-shaped factories too, just receive-side.
Outbound API
OutboundConfig, the full OutboundApi (send, endpoints, keys, tenants, replay, reconcile, messages), and every supporting type — the send-side surface of @postel/core.
@postel/http
The framework-agnostic webhook HTTP core — verify, map errors, serve JWKS — usable on any Fetch runtime or as the base for a custom adapter.