# Capture plugin (/docs/v/0.6.1/plugins/built-in/capture)



`capturePlugin` records send lifecycle events into a store and exposes it as `client.capture` — a typed property added through the plugin extension system. Pair it with the [memory adapter](/docs/v/0.6.1/guides/test-email-behavior) to assert exactly what the SDK tried to do, without a network call.

```ts title="email.test.ts"
import { createEmailClient } from "@opencoredev/email-sdk";
import { capturePlugin } from "@opencoredev/email-sdk/plugins/capture";
import { memoryProvider } from "@opencoredev/email-sdk/testing";

const email = createEmailClient({
  adapters: [memoryProvider()],
  plugins: [capturePlugin()],
});

await email.send({
  from: "Acme <hello@acme.com>",
  to: "user@example.com",
  subject: "Welcome",
  text: "Your account is ready.",
});

const sent = email.capture.events.find((event) => event.type === "afterSend");
expect(sent?.provider).toBe("memory");
expect(email.capture.events.map((event) => event.type)).toEqual(["beforeSend", "afterSend"]);
```

`email.capture` is fully typed — `capturePlugin()` is an `EmailPlugin<{ capture: EmailCaptureStore }>`, so the extension flows into the client type with no casts.

## What gets captured [#what-gets-captured]

| Event type   | Fires when                                             | Extra fields                                             |
| ------------ | ------------------------------------------------------ | -------------------------------------------------------- |
| `beforeSend` | The message enters the pipeline (once per send).       | `message`, send-option `metadata`                        |
| `afterSend`  | A provider returns a normalized response.              | `provider`, `attempt`, `response`                        |
| `retry`      | The SDK schedules another attempt on the same adapter. | `provider`, `attempt`, `nextAttempt`, `delayMs`, `error` |
| `error`      | An adapter route fails after exhausting its retries.   | `provider`, `attempt`, `error`                           |

Unlike the [observability plugin](/docs/v/0.6.1/plugins/built-in/observability), captured events hold the **full, unredacted message** — they are meant for test assertions, not production logs.

Reset between tests with `email.capture.clear()`.

## Options [#options]

<TypeTable
  type="{
  id: {
    description: &#x22;Plugin id. Override when mounting multiple capture instances.&#x22;,
    type: &#x22;string&#x22;,
    default: '&#x22;capture&#x22;',
  },
  store: {
    description: &#x22;Bring your own store instead of an internal one.&#x22;,
    type: &#x22;EmailCaptureStore&#x22;,
  },
  clientKey: {
    description: &#x22;Property name added to the client. The literal type flows into the client.&#x22;,
    type: &#x22;string&#x22;,
    default: '&#x22;capture&#x22;',
  },
}"
/>

`capturePlugin(store)` is shorthand for `capturePlugin({ store })`.

## Custom client key [#custom-client-key]

`clientKey` renames the client property, and the type follows the literal you pass:

```ts
const email = createEmailClient({
  adapters: [memoryProvider()],
  plugins: [
    capturePlugin({ id: "capture:primary", clientKey: "primaryCapture" }),
    capturePlugin({ id: "capture:audit", clientKey: "auditCapture" }),
  ],
});

email.primaryCapture.events; // typed EmailCaptureStore
email.auditCapture.clear();
```

Multiple instances need distinct `id`s (duplicate plugin ids throw) **and** distinct `clientKey`s (a second plugin extending the client with an existing key throws).

## Share a store [#share-a-store]

`createEmailCaptureStore()` creates a store you can hold a reference to directly — useful when the client is built inside a factory and your test only sees the store:

```ts
import { capturePlugin, createEmailCaptureStore } from "@opencoredev/email-sdk/plugins/capture";

const store = createEmailCaptureStore();
const email = buildTestClient({ plugins: [capturePlugin(store)] });

await email.send(message);
expect(store.events).toHaveLength(2);
```

## Capture vs memory adapter [#capture-vs-memory-adapter]

They answer different questions: `memoryProvider()` replaces the network so nothing real sends, while `capturePlugin()` records what the pipeline did — including retries and errors the adapter alone cannot show. Most tests use both. See [Test email behavior](/docs/v/0.6.1/guides/test-email-behavior) for the full testing setup.
