# Adapter model (/docs/v/0.6.1/concepts/adapter-model)



An adapter is a module that knows how to send one normalized `EmailMessage` through one service or transport. The client holds a registry of adapters keyed by routing name; everything else — defaults, fallbacks, per-send overrides — is built on those names.

```ts
import { createEmailClient } from "@opencoredev/email-sdk";
import { resend } from "@opencoredev/email-sdk/resend";
import { postmark } from "@opencoredev/email-sdk/postmark";

const email = createEmailClient({
  adapters: [
    resend({ apiKey: process.env.RESEND_API_KEY! }),
    postmark({ serverToken: process.env.POSTMARK_SERVER_TOKEN! }),
  ],
  // defaultAdapter is optional — it falls back to the first adapter ("resend" here)
});
```

Each adapter import is a separate entry point (`@opencoredev/email-sdk/resend`, `/postmark`, ...), so your bundle only contains the providers you actually send through.

## The adapter contract [#the-adapter-contract]

Every adapter does three things:

1. **Maps** supported `EmailMessage` fields to the provider's API payload.
2. **Rejects** unsupported fields with an `EmailValidationError` before any request — never silently drops data. See [field support](/docs/v/0.6.1/adapters/field-support).
3. **Normalizes** the result into `{ provider, id?, messageId?, accepted?, rejected?, raw? }` and provider failures into typed [errors](/docs/v/0.6.1/reference/errors) with retryability info.

The full interface for building your own is in the [adapter contract reference](/docs/v/0.6.1/reference/adapter-contract).

## Routing names [#routing-names]

Adapter factories register fixed routing names — `resend`, `postmark`, `sendgrid`, `cloudflare`, `unosend`, `ses`, `mailgun`, `mailersend`, `brevo`, `mailchimp`, `sparkpost`, `iterable`, `loops`, `sequenzy`, `plunk`, `mailtrap`, `scaleway`, `zeptomail`, `mailpace`, and `smtp` (the SMTP factory accepts a `name` option for running several SMTP routes side by side).

Those names are what you pass everywhere a route is selected:

```ts
// per-send override
await email.send(message, { adapter: "postmark" });

// client-level default and fallback order
createEmailClient({ adapters, defaultAdapter: "resend", fallback: ["postmark"] });

// a client pinned to one route
const transactional = email.withAdapter("postmark");
await transactional.send(message);
```

Selecting a name that is not registered throws `EmailProviderNotFoundError` — routes are explicit, never guessed.

## Escape hatches [#escape-hatches]

Adapters can expose provider-specific extras through `raw`. Use it to keep provider-coupled code next to the adapter while application code stays on the shared `send` path:

```ts
const adapter = email.adapter("resend");
console.log(adapter.raw); // { baseUrl: "https://api.resend.com" }
```

The [memory test adapter](/docs/v/0.6.1/guides/test-email-behavior) uses the same hatch to expose its `sent` array.

## Naming aliases [#naming-aliases]

`adapter` and `provider` mean the same thing throughout the API. `providers`, `defaultProvider`, `fallbackProviders`, `email.provider()`, and `email.withProvider()` are aliases kept for compatibility — new code should prefer the `adapter` spellings.

## Next [#next]

<Cards>
  <Card title="Fallbacks and retries" href="/docs/v/0.6.1/concepts/fallbacks-and-retries" description="What happens when a route fails: backoff, error classification, fallback order." />

  <Card title="Build an adapter" href="/docs/v/0.6.1/guides/authoring/create-adapter" description="Implement the contract for a provider the SDK doesn't ship yet." />
</Cards>
