---
title: "Send transactional email"
method: POST
path: "/v1/send"
tags: ["Public API"]
---

# Send transactional email

`POST /v1/send`

Send a transactional email via the public API. Automatically creates or updates the recipient contact.

**Required content:** either a `template` ID, **or** both `subject` and `body`. Template fields can be overridden by explicit request fields.

**Sender:** `from` is required unless using a template that already has a `from` configured. The sender's domain must be verified.

**Multiple recipients:** when `to` is an array, each recipient is processed sequentially with its own contact upsert and rendered email — there is no batch-send semantics. Sending is always immediate; for scheduled sends, use a Campaign.

**Attachments:** up to 10 attachments per email and 10 MB total by default. The total message size cannot exceed 40 MB.

## Headers

- `Idempotency-Key` string

## Request body

- object
  - `to` union, required — Recipient email(s). Can be a string, an object with {name, email}, or an array of either.
    - string, email — Simple email address
    - object — Recipient with name and email
      - `name` string — Recipient display name
      - `email` string, email, required — Recipient email address
    - union[] — Array of recipients (strings or objects)
      - union
        - string, email
        - object
          - `name` string — Recipient display name
          - `email` string, email, required — Recipient email address
  - `subject` string — Email subject. Required if no `template` is provided. Cannot contain newline characters.
  - `body` string — Email body (HTML). Required if no `template` is provided.
  - `template` string, uuid — Template ID to use for this email. When provided, uses the template's subject, body, from, and reply-to settings. You can override these by explicitly providing subject, body, from, or reply fields in the request. Template variables are populated from the data field.
  - `from` union — Sender email address (requires verified domain). Required unless using a template that has a 'from' address configured. Can be a string (e.g., 'hello@example.com') or an object with {name, email} (e.g., {name: 'My App', email: 'hello@example.com'}).
    - string, email — Simple email address
    - object — Sender with name and email
      - `name` string — Sender display name
      - `email` string, email, required — Sender email address
  - `name` string — **Deprecated.** Sender display name. Prefer `from: { name, email }`. Used only as a fallback when `from` is a string and no name is set there.
  - `subscribed` boolean — Subscription state to apply to the recipient. For **new** contacts, defaults to `false` on `/v1/send`. For **existing** contacts, omitting this preserves their current state — pass `true` or `false` to explicitly change it. A change emits `contact.subscribed` or `contact.unsubscribed`.
  - `data` object — Variables for template rendering and contact data updates. Each value can be: - A primitive (string, number, boolean) — saved on the contact and available as a template variable. - `null` — deletes the field from the contact. - An empty string — skipped (does not overwrite existing data). - An object `{ value, persistent: false }` — used for this send only, not stored on the contact (good for one-shot password reset codes, magic links). Reserved keys (`id`, `plunk_id`, `plunk_email`, `email`, `unsubscribeUrl`, `subscribeUrl`, `manageUrl`) are silently filtered out.
  - `headers` object — Custom email headers. Header names cannot contain `\r\n`. Header values are limited to 998 characters and cannot contain `\r\n` (header injection is rejected).
  - `reply` string, email — Reply-to address.
  - `attachments` object[] — Email attachments. Default cap: 10 attachments and 10 MB total. The full message size cannot exceed 40 MB.
    - `filename` string, required — Attachment filename. Cannot contain newline or quote characters.
    - `content` string, required — Base64-encoded file content.
    - `contentType` string, required — MIME type (e.g., `application/pdf`, `image/png`).
    - `contentId` string — Content-ID for inline images. Required when `disposition` is `inline`. Reference the image in the email body via `<img src="cid:yourContentId">`.
    - `disposition` 'attachment' | 'inline' — Use `inline` together with `contentId` to embed images in the body. Use `attachment` (the default) for downloadable files.

## Response `200`

Email queued successfully

- object
  - `success` boolean
  - `data` object
    - `emails` object[]
      - `contact` object
        - `id` string
        - `email` string
      - `email` string — Plunk email record ID. Use this to correlate webhook events (which include this ID as 'emailId' in the event data) with your send requests.
    - `timestamp` string, date-time

## Other responses

- `400` — Malformed JSON body, or an invalid `Idempotency-Key` header.
- `401` — Missing or invalid API key.
- `403` — The project is disabled and cannot send.
- `404` — The `template` ID does not exist in this project.
- `409` — Idempotency-Key already used. The request was refused, not performed.
- `422` — Request body failed schema validation. `error.errors` lists the offending fields.

## Changes

- **2026-08-09** `a13aa5345f8e` — 4 breaking, 9 warning, 30 info
  - the `template` request property type/format changed from `string`/`` to `string`/`uuid`
  - the `error` response's property type/format changed from `string`/`` to `object`/`` for status `400`
  - the `error` response's property type/format changed from `string`/`` to `object`/`` for status `401`
  - the `error` response's property type/format changed from `string`/`` to `object`/`` for status `409`
  - …39 more

[Change history](https://skmtc.dev/useplunk/apis/plunk-api/changes/v1/send/post.md)

---

[API](https://skmtc.dev/useplunk/apis/plunk-api.md) · [All operations](https://skmtc.dev/useplunk/apis/plunk-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/useplunk/plunk-api/revisions/a13aa5345f8e/schema)
