# Client (/docs/v/0.6.1/reference/client)



Complete reference for the email client: constructor options, every client method, per-send options, and the normalized response shape. For how routing behaves at runtime, see [fallbacks and retries](/docs/v/0.6.1/concepts/fallbacks-and-retries).

## `createEmailClient(options)` [#createemailclientoptions]

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

const email = createEmailClient({
  adapters: [resend({ apiKey: process.env.RESEND_API_KEY! }), smtp({ host: process.env.SMTP_HOST! })],
  defaultAdapter: "resend",
  fallback: ["smtp"],
  retry: { retries: 2 },
});
```

<TypeTable
  type="{
  adapters: {
    description: &#x22;Adapters to register, keyed by their routing names.&#x22;,
    type: &#x22;EmailProvider[]&#x22;,
    default: &#x22;[]&#x22;,
  },
  defaultAdapter: {
    description: &#x22;Routing name used when a send sets no adapter.&#x22;,
    type: &#x22;string&#x22;,
    default: &#x22;first registered adapter&#x22;,
  },
  fallback: {
    description: &#x22;Routing names tried in order after the selected adapter finally fails.&#x22;,
    type: &#x22;string[]&#x22;,
    default: &#x22;[]&#x22;,
  },
  retry: {
    description: &#x22;Per-adapter retry budget, backoff delay, and retry predicate.&#x22;,
    type: &#x22;EmailRetryConfig&#x22;,
    default: &#x22;no retries&#x22;,
  },
  hooks: {
    description: &#x22;Observe-only callbacks: beforeSend, afterSend, onError, onRetry.&#x22;,
    type: &#x22;EmailHooks&#x22;,
  },
  plugins: {
    description: &#x22;Plugins that add adapters, hooks, middleware, or client extensions.&#x22;,
    type: &#x22;EmailPlugin[]&#x22;,
    default: &#x22;[]&#x22;,
  },
}"
/>

`retry` options are documented in [fallbacks and retries](/docs/v/0.6.1/concepts/fallbacks-and-retries#retries), `hooks` in [hooks](/docs/v/0.6.1/concepts/hooks), and `plugins` in the [plugin API](/docs/v/0.6.1/plugins/api). `providers` and `defaultProvider` are accepted [aliases](#aliases).

Construction fails fast with an `EmailValidationError` for a duplicate adapter routing name (`Duplicate email adapter "x".`) or plugin id (`Duplicate email plugin "x".`), and for an empty client (`createEmailClient requires a default adapter.`). A `defaultAdapter` that is not registered throws `EmailProviderNotFoundError`.

## `email.send(message, options?)` [#emailsendmessage-options]

```ts
send(message: EmailMessage, options?: SendOptions): Promise<EmailProviderResponse>
```

Validates the [message](/docs/v/0.6.1/reference/message), builds the route `[adapter, ...fallbackAdapters]` (duplicates removed), retries each adapter per the retry config, and resolves with the first successful [response](#response). One failed route rethrows the adapter's error as-is; multiple failed routes throw `EmailSdkError` with code `all_providers_failed` — see [errors](/docs/v/0.6.1/reference/errors).

```ts
const result = await email.send(message, {
  adapter: "resend",
  fallbackAdapters: ["smtp"],
  retries: 2,
  idempotencyKey: "receipt:order_123",
  metadata: { route: "checkout.receipt" },
});
```

### SendOptions [#sendoptions]

<TypeTable
  type="{
  adapter: {
    description: &#x22;Routing name for this send. Overrides defaultAdapter.&#x22;,
    type: &#x22;string&#x22;,
  },
  fallbackAdapters: {
    description: &#x22;Replaces the client-level fallback list for this send. Pass [] to disable fallback.&#x22;,
    type: &#x22;string[]&#x22;,
  },
  retries: {
    description: &#x22;Replaces the client-level retry count for this send.&#x22;,
    type: &#x22;number&#x22;,
  },
  signal: {
    description: &#x22;AbortSignal forwarded to the provider's fetch call.&#x22;,
    type: &#x22;AbortSignal&#x22;,
  },
  idempotencyKey: {
    description: &#x22;Stable send identity passed to the adapter on every attempt. Falls back to message.idempotencyKey.&#x22;,
    type: &#x22;string&#x22;,
  },
  metadata: {
    description: &#x22;Send-scope metadata visible to hooks and middleware. Never sent in the message.&#x22;,
    type: &#x22;Record<string, unknown>&#x22;,
  },
}"
/>

`provider` and `fallbackProviders` are accepted [aliases](#aliases). Send-options `metadata` is unrelated to `message.metadata` — the message field goes to the provider, the send option only flows to [hooks](/docs/v/0.6.1/concepts/hooks).

## `email.sendBatch(messages, options?)` [#emailsendbatchmessages-options]

```ts
sendBatch(messages: SendBatchItem[], options?: SendOptions): Promise<SendBatchResult[]>
```

Sends sequentially, one message at a time in array order, and always resolves with one result per item — a failed item never throws or stops the batch:

```ts
type SendBatchResult =
  | { ok: true; index: number; response: EmailProviderResponse }
  | { ok: false; index: number; error: unknown };
```

Each item is an `EmailMessage` plus optional `adapter` and `fallbackAdapters` that override the call-level options for that item:

```ts
const results = await email.sendBatch([
  { ...welcome, adapter: "postmark" },
  receipt, // uses call-level or client defaults
]);

for (const result of results) {
  if (!result.ok) console.error(`message ${result.index} failed`, result.error);
}
```

## `email.adapter(name)` [#emailadaptername]

```ts
adapter<TProvider extends EmailProvider = EmailProvider>(name: string): TProvider
```

Returns the registered adapter or throws `EmailProviderNotFoundError`. Useful for adapter [escape hatches](/docs/v/0.6.1/concepts/adapter-model#escape-hatches) like `email.adapter("memory").raw.sent` in tests.

## `email.withAdapter(name)` [#emailwithadaptername]

```ts
withAdapter(name: string): Pick<EmailClient, "send" | "sendBatch">
```

Returns a client pinned to one route — `send` and `sendBatch` behave as if `{ adapter: name }` were passed every time. The name is checked immediately: an unregistered name throws `EmailProviderNotFoundError` at `withAdapter` time, not at send time.

```ts
const transactional = email.withAdapter("postmark");
await transactional.send(message);
```

## Properties [#properties]

<TypeTable
  type="{
  adapters: {
    description: &#x22;All registered adapters, keyed by routing name. Read-only.&#x22;,
    type: &#x22;ReadonlyMap<string, EmailProvider>&#x22;,
  },
  defaultAdapter: {
    description: &#x22;The resolved default routing name.&#x22;,
    type: &#x22;string&#x22;,
  },
}"
/>

## Response [#response]

Every successful send resolves with a normalized `EmailProviderResponse`. `provider` is always set — it names the adapter that actually delivered, which matters when a fallback route handled the send.

<TypeTable
  type="{
  provider: {
    description: &#x22;Routing name of the adapter that delivered the message.&#x22;,
    type: &#x22;string&#x22;,
    required: true,
  },
  id: {
    description: &#x22;Provider-assigned id for the send, when the API returns one.&#x22;,
    type: &#x22;string&#x22;,
  },
  messageId: {
    description: &#x22;Provider message id, when distinct from id.&#x22;,
    type: &#x22;string&#x22;,
  },
  accepted: {
    description: &#x22;Recipients the provider accepted, when reported.&#x22;,
    type: &#x22;string[]&#x22;,
  },
  rejected: {
    description: &#x22;Recipients the provider rejected, when reported.&#x22;,
    type: &#x22;string[]&#x22;,
  },
  raw: {
    description: &#x22;Untouched provider response body for debugging and provider-specific fields.&#x22;,
    type: &#x22;unknown&#x22;,
  },
}"
/>

## Aliases [#aliases]

`adapter` and `provider` mean the same thing everywhere: `providers`, `defaultProvider`, `fallbackProviders`, `email.provider(name)`, `email.providers`, and `email.withProvider(name)` are compatibility aliases for their `adapter` spellings. Prefer `adapter` in new code — see [naming aliases](/docs/v/0.6.1/concepts/adapter-model#naming-aliases).
