Hooks
Observe lifecycle events without side effects
Hooks observe the send pipeline without affecting it. They fire at specific points in the lifecycle — after validation, after render, after delivery, or on error — and let you log, record metrics, or trigger side effects without changing whether or how a notification is delivered. A failing hook never blocks a send or stops other hooks from running.
If removing a piece of logic would change whether a notification goes out, it belongs in middleware, not a hook.
Lifecycle
Four hooks fire in a fixed order during each send. onError fires on any failure regardless of where it originated.
Prop
Type
Attaching hooks
Pass hooks to createClient. Each hook accepts a single handler or an array of handlers executed in order:
const mail = createClient({
catalog,
transportsByChannel: { email: smtpTransport({ host: '...' }) },
hooks: {
onBeforeSend: ({ route, messageId }) => {
console.log(`sending ${route} [${messageId}]`);
},
onAfterSend: ({ route, result, durationMs }) => {
metrics.recordSend(route, durationMs);
},
onError: [
({ route, error, phase }) => {
errorTracker.capture(error, { route, phase });
},
({ route, error }) => {
console.error(`[${route}] ${error.message}`);
},
],
},
});Plugins can also carry hooks. Plugin hooks and client hooks merge — they don't override each other. See Plugins.
Hook context
Each hook receives a context object that builds on the previous one. Later hooks get more information:
Prop
Type
Error isolation
Hook failures are isolated from the send pipeline and from each other:
- A failing hook does not prevent the notification from being delivered.
- If one handler in an array throws, the remaining handlers still run.
- Hook errors are routed to
onErrorwithphase: 'hook'. IfonErroritself throws, the error is logged but never propagated to the caller.
This means you can safely add observability hooks without risking delivery failures.