@postbote/plugin-hooks
v1.0.0
Published
Lifecycle hooks for Postbote — intercept and cancel sends before/after delivery
Readme
@postbote/plugin-hooks
Lifecycle hooks for inspecting, modifying, or cancelling a Postbote send.
import { createPostbote } from "@postbote/core";
import { hooks } from "@postbote/plugin-hooks";
const postbote = createPostbote({
adapter,
plugins: [
hooks({
transformMessage: (message) => ({
...message,
headers: { ...message.headers, "X-Campaign": "onboarding" },
tags: { ...message.tags, campaign: "onboarding" },
}),
beforeSend: async (ctx, { cancel }) => {
if (await isSuppressed(ctx.message.to[0]?.email)) {
cancel("recipient is suppressed");
}
},
afterSend: (_ctx, result) => audit(result.messageId),
onError: (_ctx, error) => report(error.code),
}),
],
});Semantics
transformMessagereplaces the normalized email payload beforebeforeSendand the adapter. It can adjust recipients, subject, HTML/text, attachments, headers, and tags. Its return value is normalized again, including CRLF protection.beforeSendmay mutatectx.messageorctx.adapter. Errors stop the send.cancel(reason)stops the send with a non-retryableCANCELLEDPostboteError.afterSendreceives the finalSendResult.onErrorreceives the finalPostboteError.- Errors thrown by
afterSendandonErrorare ignored so observers cannot change the send outcome.
Place hooks() before failover() when the hooks should observe one logical send. Plugin order is outer to inner.
transformMessage operates on Postbote's provider-agnostic email model. Provider-specific request fields belong in the relevant adapter or a custom adapter, because only that adapter knows its provider's payload format.
License
MIT - see LICENSE.md.
