Email SDK
Reference

Message

The EmailMessage shape field by field — addresses, headers, attachments, tags vs metadata, and the exact validation rules.

EmailMessage is the one message shape every adapter accepts. This page documents each field and the validation that runs before any request; which adapter maps which field is the field support matrix.

type EmailMessage = {
  from: EmailAddress;
  to: EmailAddress | EmailAddress[];
  subject: string;
  html?: string;
  text?: string;
  cc?: EmailAddress | EmailAddress[];
  bcc?: EmailAddress | EmailAddress[];
  replyTo?: EmailAddress | EmailAddress[];
  headers?: Record<string, string> | EmailHeader[];
  attachments?: EmailAttachment[];
  tags?: EmailTag[];
  metadata?: Record<string, string | number | boolean | null>;
  idempotencyKey?: string;
};

Fields

Prop

Type

Addresses

EmailAddress is a plain string — including the Name <email> form — or an object with a display name:

from: "Acme <hello@acme.com>",
to: [{ email: "user@example.com", name: "Ada Lovelace" }, "ops@example.com"],

Adapters convert between the forms as their APIs require, so mix them freely.

Headers

Headers take two equivalent shapes; use whichever your data already has:

headers: { "X-Order-ID": "ord_123" }
// or
headers: [{ name: "X-Order-ID", value: "ord_123" }]

Attachments

type EmailAttachment = {
  filename: string;
  content?: string | Uint8Array | ArrayBuffer | Blob;
  contentEncoding?: "raw" | "base64";
  path?: string;
  contentType?: string;
  contentId?: string;
  disposition?: "attachment" | "inline";
};

Every attachment needs content or path:

  • content attaches in-memory data. String content is treated as raw (contentEncoding: "raw") and Base64-encoded for providers that need it — set contentEncoding: "base64" when the string is already Base64 so it is not double-encoded.
  • path makes the SDK read a local file at send time.

For inline images, set contentId and reference it from the HTML body with cid:, plus disposition: "inline":

await email.send({
  from: "Acme <hello@acme.com>",
  to: "user@example.com",
  subject: "Welcome",
  html: '<img src="cid:logo" alt="Acme" />',
  attachments: [
    { filename: "logo.png", path: "./logo.png", contentType: "image/png", contentId: "logo", disposition: "inline" },
  ],
});

Tags vs metadata

Both attach data to a send on the provider side, but they are different provider concepts:

  • tags are { name, value } labels for categorizing sends — provider dashboards and analytics group by them. Some providers carry only each tag's value, and Postmark and Mailtrap accept a single tag.
  • metadata is free-form key-value data (string | number | boolean | null values) the provider stores with the message and echoes in webhooks and events.

Support differs per provider — Resend has tags but no metadata, Iterable has metadata but no tags. Check the field support matrix before relying on either, especially for fallback routes.

Idempotency key

idempotencyKey gives the send a stable identity across retries. The send-options key wins when both are set; the message field is the fallback. Only Resend transmits it natively — see idempotency keys for per-adapter behavior.

Validation

send validates the message before any adapter runs and throws EmailValidationError (code validation_error) with one of these exact messages:

RuleError message
from is requiredEmail message requires a from address.
At least one to recipientEmail message requires at least one recipient.
subject is requiredEmail message requires a subject.
html or text is requiredEmail message requires either html or text content.
Attachments need dataAttachment "<filename>" requires content or path.

Adapters add a second fail-fast layer for fields their provider cannot represent:

smtp does not support these EmailMessage fields: tags, metadata.

That error fires before any request — fields are rejected, never silently dropped. The per-adapter availability of cc, bcc, replyTo, headers, attachments, tags, and metadata is the field support matrix.

On this page