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 — so 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 — 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.
