> ## Documentation Index
> Fetch the complete documentation index at: https://docs.instantcampaign.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Transactional email

> Send receipts, password resets and notifications from your own application using a template designed in InstantCampaign and a JSON payload.

Transactional emails are one-to-one messages your application triggers, such as an order confirmation, a password reset or a shipping notice. You design the email once as a template in InstantCampaign. Your app then sends one API call per email, with the recipient and a JSON `data` payload that fills the template.

This page covers the setup in the app. For the request and response fields, see the [API reference](/api-reference/introduction).

## Before you start

* A verified sending domain and sender. `fromEmail` overrides must use a domain this workspace owns.
* An [API key](/guides/developers/api-keys) with the **Transactional — send** scope. Add **Transactional — read** if your app also looks up message status.

## Set it up

<Steps>
  <Step title="Design the template">
    Build the email in the editor. Insert values from the payload with Liquid, for example `{{ data.order.id }}`, `{{ contact.first_name }}` or a loop over `data.order.line_items`. Use the Repeater or Item List block to render a list of items.
  </Step>

  <Step title="Add sample data">
    In the editor, open **Preview as…**. Under **Template data**, paste an example payload: what your app will send as `data`, without a `"data"` wrapper. Click **Update preview** to see the email rendered with it, then **Save**. If the template has already been sent through the API, pick a real payload from **Load from a recent send…**.
  </Step>

  <Step title="Check the variables">
    The **Variables** list in the same panel shows every value the template reads. Values your sample does not provide are flagged **missing**.
  </Step>

  <Step title="Mark required variables">
    Tick **required** next to any variable the email must not go out without, such as a reset link. A send that leaves a required variable empty is refused instead of delivering a broken email.
  </Step>

  <Step title="Send a test">
    Click **Send test with this preview context** to receive the email exactly as the API would render it.
  </Step>

  <Step title="Call the API from your app">
    Send one request per email with `to`, `subject`, the `templateId` and your `data`:

    ```bash theme={null}
    curl -X POST https://instantcampaign.ai/api/v1/transactional/send \
      -H "Authorization: Bearer ic_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{
        "to": "jane@example.com",
        "templateId": "tmpl_receipt",
        "subject": "Your order {{ data.order.id }}",
        "externalId": "order-10422",
        "data": { "order": { "id": "10422", "total": "£42.00" } }
      }'
    ```
  </Step>
</Steps>

## Behaviour to know about

**Duplicate protection.** Send your own id (an order id, for example) as `externalId`. If the same `externalId` is sent again, no second email goes out. The API returns the original message with `"deduplicated": true`, which makes retries in your app safe.

**Missing values.** A value the template reads but the request does not provide renders as empty, and its path is listed in `warnings` in the response. Required variables block the send with `422 MISSING_VARIABLES`. Add `"strict": true` to a request to block on every missing value.

**Validate without sending.** The validate endpoint renders a template with a payload and reports the missing variables without sending or storing anything. Run it in CI against your sample payloads to catch a renamed field before a customer gets an email with an empty link.

**Unsubscribed recipients.** By default, transactional email uses the workspace's **Transactional** send policy, which still delivers to people who unsubscribed from marketing. Bounced, complained and erased addresses are always refused. To send marketing-style mail through the API instead, pass a marketing send policy. It suppresses unsubscribed recipients and adds the compliance footer and an unsubscribe link. The link needs a contact record, so the recipient must already be a contact, or the request must include a `contact` object (otherwise `422 MARKETING_NEEDS_CONTACT`). Policies are managed under **Settings → Send policies**.

**Contacts.** Include a `contact` object to create or update the recipient as a contact before sending. Their subscription status is never changed. If the plan's contact limit is reached, the email still goes out but no contact is created.

**Attachments.** Up to 20 files, 3 MB each and 10 MB in total, sent as base64. Inline images are supported.

**Rate limit.** 100 sends per minute per workspace, 100 validations per minute, and 100 status lookups per minute.

## Track what was sent

* **Analytics → Message Log** lists transactional emails next to campaign and journey sends, with delivery status, opens and clicks. Each message links to the API request that created it.
* **Settings → Developers → API log** shows the exact payload of every send. See [API request log](/guides/developers/api-request-log).
* Subscribe a [webhook](/guides/developers/webhooks) to `TRANSACTIONAL_SENT`, `EMAIL_DELIVERED` and `EMAIL_BOUNCED` to get status updates on your own server.

A message's status moves from `queued` to `sent`, then to `delivered`, `bounced` or `complained` as the email provider reports back, or to `failed` if the provider refused it.

## Troubleshooting

| Response                              | Cause                                                                                      | Fix                                                                     |
| ------------------------------------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------- |
| `400` "Template error: …"             | Liquid syntax error in the template or subject                                             | Fix the template; nothing was sent                                      |
| `403` on `fromEmail`                  | The address is not on a domain this workspace owns                                         | Verify the domain, or leave `fromEmail` out                             |
| `422 MISSING_VARIABLES`               | A required variable (or, with `strict`, any variable) is missing                           | Add the paths listed in `missing[]` to `data`                           |
| `422` "Recipient email is suppressed" | The address bounced, complained or was erased (or, under a marketing policy, unsubscribed) | Nothing to fix in the request; the address cannot be mailed             |
| `400 SEND_POLICY_NOT_FOUND`           | The `sendPolicy` key or id does not exist                                                  | Check it under **Settings → Send policies**                             |
| Email arrived with blank values       | Variables were not required, so they rendered empty                                        | Check `warnings` in the response, and mark those variables **required** |
