# Send transactional email (/api-reference/public-api/sendEmail)

`POST /v1/send`

Base URL: `https://next-api.useplunk.com`

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.

## Request body

- `to`: string (email) | object | array<string (email) | object> (required) — Recipient email(s). Can be a string, an object with {name, email}, or an array of either.
- `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 — 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`: string (email) | object — 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'}).
- `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`: array<object> — Email attachments. Default cap: 10 attachments and 10 MB total. The full message size cannot exceed 40 MB.
  items:
    - `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`: enum ("attachment" | "inline") — Use `inline` together with `contentId` to embed images in the body. Use `attachment` (the default) for downloadable files.

Example:

```json
{
  "to": "user@example.com"
}
```

## Responses

### `200` — Email queued successfully

- `success`: boolean
- `data`: object
  - `emails`: array<object>
    items:
      - `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)

```json
{
  "success": false,
  "data": {
    "emails": [
      {
        "contact": {
          "id": "string",
          "email": "string"
        },
        "email": "string"
      }
    ],
    "timestamp": "2026-05-17T18:27:16.094Z"
  }
}
```

### `400` — Bad request

- `code`: integer
- `error`: string
- `message`: string
- `time`: integer

```json
{
  "code": 0,
  "error": "string",
  "message": "string",
  "time": 0
}
```

### `401` — Unauthorized

- `code`: integer
- `error`: string
- `message`: string
- `time`: integer

```json
{
  "code": 0,
  "error": "string",
  "message": "string",
  "time": 0
}
```

## Example request

```bash
curl -X POST 'https://next-api.useplunk.com/v1/send' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"to":"user@example.com"}'
```
