# Field support (/docs/v/0.6.1/adapters/field-support)



Email SDK keeps one message shape, but provider APIs do not expose the same features. Every adapter either **maps** a field or **rejects** it with an `EmailValidationError` before calling the provider — never a silent drop. This page is the authoritative matrix.

**Reading the tables:** `Yes` = mapped, `No` = rejected before the request, `Values` = each tag's value is sent, `One tag` = the provider API represents a single tag and a second one fails fast.

## API adapters [#api-adapters]

The best fit when your app needs CC, BCC, reply-to, custom headers, tags, metadata, or attachments.

| Adapter                 | CC  | BCC | Reply-To | Headers | Metadata | Tags    | Attachments |
| ----------------------- | --- | --- | -------- | ------- | -------- | ------- | ----------- |
| Resend                  | Yes | Yes | Yes      | Yes     | No       | Yes     | Yes         |
| Postmark                | Yes | Yes | Yes      | Yes     | Yes      | One tag | Yes         |
| SendGrid                | Yes | Yes | Yes      | Yes     | Yes      | Values  | Yes         |
| Cloudflare              | Yes | Yes | Yes      | Yes     | No       | No      | Yes         |
| Unosend                 | Yes | Yes | Yes      | Yes     | No       | Values  | Yes         |
| AWS SES                 | Yes | Yes | Yes      | Yes     | No       | Yes     | Yes         |
| Mailgun                 | Yes | Yes | Yes      | Yes     | Yes      | Values  | Yes         |
| MailerSend              | Yes | Yes | Yes      | Yes     | No       | Values  | Yes         |
| Brevo                   | Yes | Yes | Yes      | Yes     | Yes      | Values  | Yes         |
| Mailchimp Transactional | Yes | Yes | No       | Yes     | Yes      | Values  | Yes         |
| Mailtrap                | Yes | Yes | Yes      | Yes     | Yes      | One tag | Yes         |

## Narrow adapters [#narrow-adapters]

Useful services whose public APIs cover less of the `EmailMessage` shape. The SDK keeps them safe by rejecting what they cannot represent.

| Adapter   | CC  | BCC | Reply-To | Headers | Metadata | Tags   | Attachments |
| --------- | --- | --- | -------- | ------- | -------- | ------ | ----------- |
| SparkPost | No  | No  | Yes      | Yes     | Yes      | Values | Yes         |
| Iterable  | No  | No  | No       | No      | Yes      | No     | No          |
| Loops     | No  | No  | No       | No      | Yes      | No     | Yes         |
| Sequenzy  | No  | No  | Yes      | No      | Yes      | No     | Yes         |
| Plunk     | No  | No  | Yes      | Yes     | Yes      | No     | Yes         |
| Scaleway  | Yes | Yes | Yes      | Yes     | No       | No     | Yes         |
| ZeptoMail | Yes | Yes | Yes      | No      | No       | No     | Yes         |
| MailPace  | Yes | Yes | Yes      | No      | No       | No     | No          |

## SMTP transport [#smtp-transport]

Built in, no Nodemailer. SMTP maps address fields and headers directly into the message but has no provider-side concepts like tags or metadata.

| Adapter | CC  | BCC | Reply-To | Headers | Metadata | Tags | Attachments |
| ------- | --- | --- | -------- | ------- | -------- | ---- | ----------- |
| SMTP    | Yes | Yes | Yes      | Yes     | No       | No   | No          |

## Choosing compatible routes [#choosing-compatible-routes]

A [fallback route](/docs/v/0.6.1/concepts/fallbacks-and-retries) is only safe when the backup adapter supports every field your messages actually use. Pick routes from your message shape, not provider popularity:

| Your messages use…                | Compatible route examples                        |
| --------------------------------- | ------------------------------------------------ |
| Addresses, subject, body, headers | Almost anything — Resend + SMTP works            |
| Attachments                       | Resend, Postmark, SendGrid, Mailgun, MailerSend… |
| Metadata for analytics or routing | Postmark, SendGrid, Mailgun, Brevo, Mailtrap     |
| Tags and metadata together        | SendGrid, Mailgun, Brevo, Mailtrap               |

This pair works because both routes can carry every field used:

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

await email.send({
  from: "Acme <hello@acme.com>",
  to: "user@example.com",
  replyTo: "support@example.com",
  subject: "Password reset",
  text: "Use this link to reset your password.",
  headers: { "X-Template": "password-reset" },
});
```

The same client with `tags` or `metadata` on the message would fail fast on the SMTP route instead of dropping those fields — which is exactly the point.

Before adding a backup route, run through this checklist:

* Does the backup adapter support every `EmailMessage` field your messages use?
* Does it preserve attachments when receipts, exports, or files matter?
* Does it preserve `metadata` or `tags` your app relies on for provider dashboards, analytics, or routing?
* Does it support `replyTo` and `headers` if support workflows depend on them?
* Has the backup provider account been live-verified (one real smoke send) in the target environment?

## Attachment rules [#attachment-rules]

Attachment `content` is treated as raw data by default and Base64-encoded for APIs that need it:

```ts
attachments: [{ filename: "receipt.txt", content: "Thanks for your order.", contentType: "text/plain" }];
```

Already have Base64? Mark it so it is not double-encoded:

```ts
attachments: [
  { filename: "receipt.pdf", content: base64Pdf, contentEncoding: "base64", contentType: "application/pdf" },
];
```

Adapters with attachment support can also read from disk via `path`:

```ts
attachments: [{ filename: "receipt.pdf", path: "./receipt.pdf", contentType: "application/pdf" }];
```

## Adapter-specific notes [#adapter-specific-notes]

<Accordions type="multiple">
  <Accordion title="Loops — attachments need account enablement">
    Loops attachments map to the provider's transactional attachments payload, which Loops only
    enables per account on request. Omit `attachments` until your account has it.
  </Accordion>

  <Accordion title="Sequenzy — reserved metadata keys and one body field">
    Metadata maps to transactional `variables`, except reserved keys that map to provider fields:
    `sequenzySlug` (template slug), `sequenzyPreview`, and `subscriberExternalId` /
    `sequenzySubscriberExternalId`. Sequenzy also has a single body field — when a message has both
    `html` and `text`, the HTML body is sent and the text body stays available for other routes.
  </Accordion>

  <Accordion title="Iterable — template sends, one recipient">
    Iterable target sends render a campaign template: `subject`, `html`, `text`, and `from` flow
    into template `dataFields`, and `metadata` maps to webhook metadata. CC, BCC, reply-to,
    headers, tags, and attachments are rejected, and each send targets exactly one recipient.
  </Accordion>

  <Accordion title="Cloudflare — plain addresses, recipient caps">
    Cloudflare Email Sending requires plain email addresses (no display names) and caps a message
    at 50 total recipients across to/cc/bcc with a single reply-to.
  </Accordion>
</Accordions>

<Callout title="Local checks are not deliverability">
  These checks stop bad requests before they leave your process. Live delivery still needs a ready
  provider account: verified senders or domains, the right region, API scopes, and any sandbox or
  allow-list rules. Finish with one real smoke send per route.
</Callout>
