Plugins
Package reusable send behavior (adapters, hooks, middleware, and typed client extensions) into one plugins array.
A plugin bundles send-pipeline behavior into one reusable unit you mount with plugins: [...]. One plugin can register adapters, transform messages before validation, observe every attempt, and add typed helpers to the client. Shared policy lives in one place instead of being copied around every createEmailClient call.
import { createEmailClient } from "@opencoredev/email-sdk";
import { defaultsPlugin } from "@opencoredev/email-sdk/plugins/defaults";
import { observabilityPlugin } from "@opencoredev/email-sdk/plugins/observability";
import { resend } from "@opencoredev/email-sdk/resend";
export const email = createEmailClient({
adapters: [resend({ apiKey: process.env.RESEND_API_KEY! })],
plugins: [
defaultsPlugin({ headers: { "X-App": "acme" }, replyTo: "support@acme.com" }),
observabilityPlugin({ log: (event) => console.log(event.type, event.provider) }),
],
});What a plugin can do
A plugin is a plain object with an id and any combination of four capabilities:
| Capability | Field | What it does |
|---|---|---|
| Add routes | adapters | Registers providers into the client, same as the adapters option. This is how community provider packages ship one-call setup. |
| Transform | middleware | beforeSend runs once per send, before validation, and can replace the message or options. afterSend / onError observe outcomes. |
| Observe | hooks | The same hooks as the client option: per-attempt, observe-only, failures swallowed. |
| Extend | extendClient | Adds typed properties to the returned client, like email.capture from the capture plugin. |
Middleware is the capability only plugins have: client-level hooks can watch a send, but only plugin middleware can change it.
Built-in plugins
Three plugins ship with the SDK, each from its own entry point:
| Plugin | Import | Use it when |
|---|---|---|
| Defaults | @opencoredev/email-sdk/plugins/defaults | Every send needs the same headers, tags, reply-to, or idempotency prefix. |
| Observability | @opencoredev/email-sdk/plugins/observability | You want logs, metrics, or traces without leaking recipients or bodies. |
| Capture | @opencoredev/email-sdk/plugins/capture | Tests need to assert attempted sends, retries, responses, and errors. |
Registration rules
Plugins register in array order, and the order is deterministic:
- Direct
adaptersregister first, then each plugin's adapters in plugin order. - Middleware and hooks run in plugin order; plugin hooks run before client
hooks. - Duplicate plugin ids and duplicate adapter names throw an
EmailValidationError. Mount two instances of the same plugin with distinctids and distinct extension keys, e.g.capturePlugin({ id: "capture:audit", clientKey: "auditCapture" }).
The full rules, event shapes, and error cases are in the plugin API reference.
Explore
Defaults plugin
Merge org-wide headers, tags, reply-to, and idempotency prefixes into every send.
Observability plugin
Redacted sent/retry/error events for logs, metrics, and traces.
Capture plugin
Record send lifecycle events in tests via a typed client.capture store.
Writing plugins
Anatomy of EmailPlugin: hooks vs middleware, plugin adapters, typed extensions.
Plugin API reference
EmailPlugin, EmailPluginContext, middleware events, and extension typing.
Community plugins
Third-party adapters and plugins, plus how to list your own.
