Email SDK
Adapters

Field support

Which EmailMessage fields each adapter maps — and which it rejects before the request.

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

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

AdapterCCBCCReply-ToHeadersMetadataTagsAttachments
ResendYesYesYesYesNoYesYes
PostmarkYesYesYesYesYesOne tagYes
SendGridYesYesYesYesYesValuesYes
CloudflareYesYesYesYesNoNoYes
UnosendYesYesYesYesNoValuesYes
AWS SESYesYesYesYesNoYesYes
MailgunYesYesYesYesYesValuesYes
MailerSendYesYesYesYesNoValuesYes
BrevoYesYesYesYesYesValuesYes
Mailchimp TransactionalYesYesNoYesYesValuesYes
MailtrapYesYesYesYesYesOne tagYes

Narrow adapters

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

AdapterCCBCCReply-ToHeadersMetadataTagsAttachments
SparkPostNoNoYesYesYesValuesYes
IterableNoNoNoNoYesNoNo
LoopsNoNoNoNoYesNoYes
SequenzyNoNoYesNoYesNoYes
PlunkNoNoYesYesYesNoYes
ScalewayYesYesYesYesNoNoYes
ZeptoMailYesYesYesNoNoNoYes
MailPaceYesYesYesNoNoNoNo

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.

AdapterCCBCCReply-ToHeadersMetadataTagsAttachments
SMTPYesYesYesYesNoNoNo

Choosing compatible routes

A fallback route 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, headersAlmost anything — Resend + SMTP works
AttachmentsResend, Postmark, SendGrid, Mailgun, MailerSend…
Metadata for analytics or routingPostmark, SendGrid, Mailgun, Brevo, Mailtrap
Tags and metadata togetherSendGrid, Mailgun, Brevo, Mailtrap

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

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 content is treated as raw data by default and Base64-encoded for APIs that need it:

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

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

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

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

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

Adapter-specific notes

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.

On this page