# Transactional Email API

> Learn how to send transactional emails programmatically using the Broadcast API for timely and personalized communication with your users.

Source: https://sendbroadcast.net/docs/api-transactional-email

The Transactional Email API allows you to programmatically send individual, personalized emails triggered by specific events in your application.

## Required Permissions

All endpoints require authentication via an API token with appropriate permissions:

- View transactional email details (Read)
- Send transactional emails (Write)

## Transactional Email Object

The transactional email object contains the following fields:

| Field | Description |
| --- | --- |
| `id` | The unique identifier of the transactional email |
| `recipient_email` | The email address of the recipient |
| `recipient_name` | The name of the recipient (optional) |
| `subject` | The subject line of the email |
| `body` | The content of the email |
| `preheader` | Preview text that appears in email clients |
| `reply_to` | Reply-to email address for this transactional email |
| `sender_identity_id` | ID of the sender identity (From address) to send as; omit to use the channel default |
| `queue_at` | The scheduled time for sending the email |
| `sent_at` | The time when the email was sent |
| `created_at` | The time when the record was created |
| `updated_at` | The time when the record was last updated |
| `status_url` | URL to check the status of this transactional email |

## Send Transactional Email

```
POST /api/v1/transactionals.json
```

### Parameters

- `to` (required): Email address of the recipient
- `subject` (required, unless `template_id` is provided): Subject line of the email
- `body` (required, unless `template_id` is provided): Content of the email, which can be either a simple string or HTML. In either case, Broadcast will wrap the content in the appropriate html and body tags. If you want to incorporate an entire HTML design, just send the contents within the body tag.
- `template_id` (optional): ID of an email template to use. The template's body, subject, and preheader are used as defaults. Explicit `body`, `subject`, or `preheader` params override the template values.
- `content_type` (optional): Set to `blocks` to send a design from the [Drag-and-Drop Email Builder](https://sendbroadcast.net/docs/block-email-builder). Pass the document as `blocks_json` and omit `body`; Broadcast renders the HTML and sends it as a complete document.
- `blocks_json` (optional): A block document (`{ "version": 1, "blocks": [...] }`). The response echoes `content_type` and `blocks_json` alongside the rendered `body`.
- `reply_to` (optional): Reply-to email address for this transactional email
- `sender_identity_id` (optional): ID of the sender identity to send as (must belong to the channel); omit to use the channel default
- `include_unsubscribe_link` (optional): Set to `true` to include an unsubscribe link. Defaults to `false`.
- `html_body_add_footer` (optional): For `content_type: "html"`, whether the channel footer is appended to the body (default: true, or the template's own setting when `template_id` is given). An email with its own unsubscribe link (a footer block that shows the link, or a `{{ unsubscribe_url }}` link that every subscriber sees) never gets the channel footer on top, whatever this value (2.46.0 or later). Without an own link, rich text and block emails always get the channel footer, and HTML emails follow this value. Only matters when `include_unsubscribe_link` is `true`. A value that disagrees with the document is replaced, and the response carries a `parameter_overridden` warning (see [API Response Warnings](https://sendbroadcast.net/docs/api-response-warnings#the-footer-of-a-block-email)).
- `template_id` sends what the template is: its content type and block design come with it, plus its footer setting. `subject`, `body` and `preheader` still override the template's.

### Request

```bash
curl -X POST \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  --data-raw '{
    "to": "recipient@example.com",
    "subject": "Welcome to Our Service",
    "body": "Thank you for signing up!",
    "reply_to": "support@example.com"
  }' \
  https://your-broadcast-domain.com/api/v1/transactionals.json
```

### Response

```json
{
  "id": "123",
  "recipient_email": "recipient@example.com",
  "recipient_name": null,
  "subject": "Welcome to Our Service",
  "body": "Thank you for signing up!",
  "preheader": null,
  "reply_to": "support@example.com",
  "queue_at": "2024-03-21T10:00:00Z",
  "sent_at": null,
  "created_at": "2024-03-21T09:00:00Z",
  "updated_at": "2024-03-21T09:00:00Z",
  "status_url": "https://your-broadcast-domain.com/api/v1/transactionals/123.json"
}
```

**Note:** The `recipient_name` field is populated from subscriber data if the email matches an existing subscriber. The `preheader` field is reserved for future use.

If the request is invalid, the response code will be 422 (Unprocessable Entity). The error message will be returned in the response body:

```json
{
  "errors": {
    "recipient_email": ["can't be blank"],
    "subject": ["can't be blank"],
    "body": ["can't be blank"]
  }
}
```

## Send Transactional Email Using a Template

Instead of providing the email content inline, you can reference an existing template by its ID. The template's `body`, `subject`, and `preheader` are used as defaults, and any explicit parameters override the template values.

### Request

```bash
curl -X POST \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  --data-raw '{
    "to": "recipient@example.com",
    "template_id": 123
  }' \
  https://your-broadcast-domain.com/api/v1/transactionals.json
```

You can override specific template fields:

```bash
curl -X POST \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  --data-raw '{
    "to": "recipient@example.com",
    "template_id": 123,
    "subject": "Custom subject override"
  }' \
  https://your-broadcast-domain.com/api/v1/transactionals.json
```

The template must belong to the same channel as the API token. If the template is not found, a 422 error is returned.

## Double Opt-In via Transactional Email

Send a confirmation email using your own email content via the transactional API. When `double_opt_in` is set to `true`, a subscriber is automatically created (if one doesn't exist) and `{{ confirmation_url }}` becomes available as a Liquid variable in your email body.

### Parameters

- `double_opt_in` (required): Set to `true` to enable double opt-in
- `confirmation_template_id` (optional): ID of a confirmation template whose page copy (heading and body, customizable per page state) is displayed on the [confirmation page](https://sendbroadcast.net/docs/confirmation-pages) after the subscriber clicks the link. If omitted, the confirmation page resolves to the channel's default confirmation template, then to Broadcast's built-in text in the page's language.
- `subscriber` (optional): An object with subscriber attributes for the auto-created subscriber
  - `first_name`: First name
  - `last_name`: Last name

### Request

```bash
curl -X POST \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  --data-raw '{
    "to": "user@example.com",
    "subject": "Confirm your subscription to Acme Newsletter",
    "body": "<p>Hi {{ first_name }},</p><p>Thanks for signing up! Please <a href=\"{{ confirmation_url }}\">confirm your subscription</a> to start receiving our newsletter.</p><p>If you did not sign up, you can ignore this email.</p>",
    "double_opt_in": true,
    "subscriber": {
      "first_name": "John",
      "last_name": "Doe"
    }
  }' \
  https://your-broadcast-domain.com/api/v1/transactionals.json
```

### Response

```json
{
  "id": 123,
  "recipient_email": "user@example.com",
  "subject": "Confirm your subscription to Acme Newsletter",
  "status": "queued",
  "confirmation_status": "pending",
  "confirmation_url": "https://your-broadcast-domain.com/confirm/abc123token",
  "status_url": "https://your-broadcast-domain.com/api/v1/transactionals/123.json"
}
```

### What happens

1. A subscriber is auto-created (unconfirmed) if one doesn't already exist
2. A confirmation record is created with a unique token
3. Your transactional email is sent with `{{ confirmation_url }}` resolved to the real confirmation link
4. When the subscriber clicks the link, they are confirmed

### Behavior by subscriber state

| Subscriber State | Behavior |
| --- | --- |
| Does not exist | Created as unconfirmed. Transactional email sent with `{{ confirmation_url }}` resolved. |
| Exists, unconfirmed | New confirmation created. Transactional email sent with fresh `{{ confirmation_url }}`. |
| Exists, confirmed | `double_opt_in` ignored. Transactional email sent normally. `{{ confirmation_url }}` renders blank. |

### Available Liquid Variables

When `double_opt_in` is `true`, the following variables are available in your email body in addition to the standard transactional variables:

| Variable | Description |
| --- | --- |
| `{{ confirmation_url }}` | The confirmation link (only populated when subscriber is unconfirmed) |
| `{{ email }}` | Recipient's email address |
| `{{ first_name }}` | Recipient's first name |
| `{{ last_name }}` | Recipient's last name |
| `{{ name }}` | Full name |
| `{{ unsubscribe_url }}` | Unsubscribe link (if `include_unsubscribe_link: true`). Empty otherwise; a link you wrote to it is then removed with its text (2.46.0 or later) |
| `{{ preferences_url }}` | The subscriber's preference page (2.46.0 or later). Empty when the recipient is not a subscriber; a footer block then leaves its preference link out, and a link you wrote to it is removed with its text |

## Idempotent Requests

Sending the same transactional email twice (for example, when a network timeout
makes your client retry) costs real deliverability. To make retries safe, pass an
`Idempotency-Key` header on `POST /api/v1/transactionals`: an opaque string of your
choosing (up to 255 characters), unique per logical send.

```
Idempotency-Key: order-12345-receipt
```

Behavior:

- **First request** with a key is processed normally and its response is stored.
- **Retrying with the same key and the same request body** does not send again: the
  stored response is replayed, with an `Idempotency-Replayed: true` response header.
- **Reusing a key with a different request body** fails with `422`: a key
  identifies one logical request, never two.
- **Retrying while the first attempt is still processing** returns `409`; retry
  after a short delay.
- Keys are scoped to the API token that sent them and expire after 24 hours. The
  request identity covers the full URL including the query string: the same key
  with a different `broadcast_channel_id` is a different request.
- Responses with server errors (5xx) are not stored, and neither is any response
  produced by a raised error: such retries are processed fresh.
- Requests without the header behave exactly as before.

## Get Transactional Email Details

```
GET /api/v1/transactionals/:id.json
```

### Parameters

- `id` (required): The ID of the transactional email

### Request

```bash
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  https://your-broadcast-domain.com/api/v1/transactionals/123.json
```

### Response

```json
{
  "id": "123",
  "recipient_email": "recipient@example.com",
  "recipient_name": null,
  "subject": "Welcome to Our Service",
  "body": "Thank you for signing up!",
  "preheader": null,
  "reply_to": "support@example.com",
  "queue_at": "2024-03-21T10:00:00Z",
  "sent_at": "2024-03-21T10:00:05Z",
  "status": "sent",
  "error": null,
  "created_at": "2024-03-21T09:00:00Z",
  "updated_at": "2024-03-21T10:00:05Z",
  "status_url": "https://your-broadcast-domain.com/api/v1/transactionals/123.json"
}
```

`status` is one of `pending`, `queued`, `sent` or `failed`. When it is `failed`, `error` says why, for example when the recipient is on a suppression list:

```json
{
  "id": "124",
  "recipient_email": "suppressed@example.com",
  "sent_at": null,
  "status": "failed",
  "error": "[Unknown] Email recipient is on the global suppression list"
}
```

## Unsubscribe Behavior

Transactional emails bypass unsubscribe status. Unlike marketing emails (broadcasts and sequences), transactional emails will be delivered even if the recipient has unsubscribed from your mailing list. They are not sent to suppressed addresses (bounces, complaints, and your suppression list); the status endpoint reports those as `failed` with the reason in `error`.

A transactional email carries no unsubscribe link and no `List-Unsubscribe` header unless you send it with `include_unsubscribe_link: true`, so mail clients do not offer an Unsubscribe button on receipts or sign-in codes.

This is by design and follows industry standards (CAN-SPAM, GDPR) which distinguish between marketing communications and essential service-related messages like:

- Password reset emails
- OTP/verification codes
- Account confirmation emails
- Order receipts and shipping notifications

**Exceptions**: Transactional emails will still be blocked if:

- The recipient is a **redacted subscriber** (GDPR data deletion request)
- The recipient is on the **global suppression list** (typically due to hard bounces)

## Notes

- When sending a transactional email, if no specific queue time is provided, the email will be queued immediately.
- If the recipient's email matches an existing subscriber in your database, the transactional email will be automatically associated with that subscriber's record.
- All API requests must include a valid API token with the appropriate permissions in the Authorization header.
