# Message (/docs/v/0.6.1/reference/message)



`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](/docs/v/0.6.1/adapters/field-support).

```ts
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 [#fields]

<TypeTable
  type="{
  from: {
    description: &#x22;Sender address.&#x22;,
    type: &#x22;EmailAddress&#x22;,
    required: true,
  },
  to: {
    description: &#x22;One or more recipients.&#x22;,
    type: &#x22;EmailAddress | EmailAddress[]&#x22;,
    required: true,
  },
  subject: {
    description: &#x22;Message subject.&#x22;,
    type: &#x22;string&#x22;,
    required: true,
  },
  html: {
    description: &#x22;HTML body. At least one of html or text is required.&#x22;,
    type: &#x22;string&#x22;,
  },
  text: {
    description: &#x22;Plain-text body. At least one of html or text is required.&#x22;,
    type: &#x22;string&#x22;,
  },
  cc: {
    description: &#x22;Carbon-copy recipients.&#x22;,
    type: &#x22;EmailAddress | EmailAddress[]&#x22;,
  },
  bcc: {
    description: &#x22;Blind-carbon-copy recipients.&#x22;,
    type: &#x22;EmailAddress | EmailAddress[]&#x22;,
  },
  replyTo: {
    description: &#x22;Reply-to addresses.&#x22;,
    type: &#x22;EmailAddress | EmailAddress[]&#x22;,
  },
  headers: {
    description: &#x22;Custom message headers, as an object or a list.&#x22;,
    type: &#x22;Record<string, string> | EmailHeader[]&#x22;,
  },
  attachments: {
    description: &#x22;Files to attach, from memory or disk.&#x22;,
    type: &#x22;EmailAttachment[]&#x22;,
  },
  tags: {
    description: &#x22;Provider-side categorical labels.&#x22;,
    type: &#x22;EmailTag[]&#x22;,
  },
  metadata: {
    description: &#x22;Provider-side key-value data attached to the send.&#x22;,
    type: &#x22;Record<string, string | number | boolean | null>&#x22;,
  },
  idempotencyKey: {
    description: &#x22;Stable send identity, used when send options set none.&#x22;,
    type: &#x22;string&#x22;,
  },
}"
/>

## Addresses [#addresses]

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

```ts
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]

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

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

## Attachments [#attachments]

```ts
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"`:

```ts
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 [#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](/docs/v/0.6.1/adapters/field-support) before relying on either, especially for fallback routes.

## Idempotency key [#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](/docs/v/0.6.1/concepts/fallbacks-and-retries#idempotency-keys) for per-adapter behavior.

## Validation [#validation]

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

| Rule                         | Error message                                         |
| ---------------------------- | ----------------------------------------------------- |
| `from` is required           | `Email message requires a from address.`              |
| At least one `to` recipient  | `Email message requires at least one recipient.`      |
| `subject` is required        | `Email message requires a subject.`                   |
| `html` or `text` is required | `Email message requires either html or text content.` |
| Attachments need data        | `Attachment "<filename>" requires content or path.`   |

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

```txt
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](/docs/v/0.6.1/adapters/field-support).
