Guides
Write an Adapter
Build a provider integration that follows the same behavioral contract as official adapters.
Use defineAdapter() and the adapter contract suite for a custom provider.
pnpm add @postbote/core
pnpm add -D @postbote/adapter-contract vitestYour adapter should map provider responses into a message ID and normalized errors. Never include request headers or credentials in PostboteError.cause.
import {
defineAdapter,
httpStatusToErrorCode,
PostboteError,
} from "@postbote/core";
export const acmeMail = (apiKey: string) =>
defineAdapter({
name: "acme-mail",
async send(message, { signal }) {
const response = await fetch("https://mail.acme.test/send", {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}` },
body: JSON.stringify(message),
signal,
});
const body = await response.json().catch(() => undefined);
if (!response.ok) {
throw new PostboteError(response.statusText, {
code: httpStatusToErrorCode(response.status),
provider: "acme-mail",
cause: { status: response.status, body },
});
}
return { messageId: body.id, raw: body };
},
});Then call runAdapterContractTests() from one contract test file. The suite checks error codes, retryability, message IDs, abort propagation, and credential leakage. Use skip only for a provider behavior that cannot be generated synchronously.