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

# Send transactional email

> Send order confirmations, OTPs, and notifications with the Mailercloud Email API from PHP, Node.js, or any HTTP client.

Transactional emails (order confirmations, password resets, OTPs) use the dedicated **Email API** at `https://email-api.mailercloud.com` — separate from the campaign API.

Set `version` to `"1.0"` when sending `html` only, or `"2.0"` when including `amp_html`.

<CodeGroup>
  ```bash cURL theme={null}
  curl --request POST \
    --url https://email-api.mailercloud.com/email \
    --header 'Authorization: YOUR_API_KEY' \
    --header 'Content-Type: application/json' \
    --data '{
      "version": "1.0",
      "email": {
        "from": "orders@yourdomain.com",
        "fromName": "Acme Store",
        "subject": "Your order is confirmed",
        "html": "<html><body><p>Thanks for your order!</p></body></html>",
        "text": "Thanks for your order!",
        "recipients": {
          "to": [{ "name": "Jane", "email": "jane@example.com" }]
        }
      }
    }'
  ```

  ```php PHP theme={null}
  <?php
  $payload = [
    'version' => '1.0',
    'email' => [
      'from' => 'orders@yourdomain.com',
      'fromName' => 'Acme Store',
      'subject' => 'Your order is confirmed',
      'html' => '<html><body><p>Thanks for your order!</p></body></html>',
      'text' => 'Thanks for your order!',
      'recipients' => [
        'to' => [['name' => 'Jane', 'email' => 'jane@example.com']],
      ],
    ],
  ];

  $ch = curl_init('https://email-api.mailercloud.com/email');
  curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
      'Authorization: YOUR_API_KEY',
      'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode($payload),
  ]);
  $response = curl_exec($ch);
  curl_close($ch);
  echo $response;
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://email-api.mailercloud.com/email", {
    method: "POST",
    headers: {
      Authorization: "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      version: "1.0",
      email: {
        from: "orders@yourdomain.com",
        fromName: "Acme Store",
        subject: "Your order is confirmed",
        html: "<html><body><p>Thanks for your order!</p></body></html>",
        text: "Thanks for your order!",
        recipients: {
          to: [{ name: "Jane", email: "jane@example.com" }],
        },
      },
    }),
  });
  console.log(await response.json());
  ```
</CodeGroup>

## Per-recipient personalization (mail merge)

To personalize each recipient's email in a single request — `Hi {{first_name}}, your order {{order.id}} shipped` — use the [mail merge endpoint](/api-reference/email/send-personalized-email): same request structure at `POST /email-api`, plus a `merge_vars` object on each `recipients.to[]` entry.

## Attachments

Pass an `attachments` array with a `name` and a publicly fetchable `url` per file — see the [full reference](/api-reference/email/send-email).

## Custom headers

Add your own headers with `email.headers` — for example a correlation id, or a one-click
unsubscribe link of your own:

```json theme={null}
"headers": {
  "X-Order-Reference": "10482",
  "List-Unsubscribe": "<https://example.com/unsubscribe?u=10482>, <mailto:unsubscribe@example.com>",
  "List-Unsubscribe-Post": "List-Unsubscribe=One-Click"
}
```

`List-Unsubscribe` values must be angle-bracketed `https://` or `mailto:` URIs, and
`List-Unsubscribe-Post` must be exactly `List-Unsubscribe=One-Click`. Headers Mailercloud sets
itself — `From`, `Message-ID`, `DKIM-Signature` and similar — are rejected with `400`; use
`from`, `metadata.messageId` and the other request fields instead. Limits are 50 headers, 8 KB
per value and 64 KB in total. The [full reference](/api-reference/email/send-email) lists every
reserved header.

## Custom data on webhooks

Add your own keys to `metadata.custom` and Mailercloud returns them on **every**
[transactional webhook](/guides/webhooks) event for that message — Sent, Delivery, Open, Bounce,
Spam, Reject — so you can tie an event back to your own order, case, or user record with no lookup.

```json theme={null}
"metadata": {
  "campaignType": "TRANSACTIONAL",
  "messageId": "order-10482-confirmation",
  "custom": {
    "order_ref": "10482",
    "repo_id": "412f80f9-0b34-41c3-bb54-88a13acef7a9"
  }
}
```

Each event then carries your keys back, inside a `custom` object, as strings:

```json theme={null}
"custom": {
  "order_ref": "10482",
  "repo_id": "412f80f9-0b34-41c3-bb54-88a13acef7a9"
}
```

* Up to **10** custom keys per message. Key names are **≤ 50 characters** using `A–Z a–z 0–9 - _`;
  values are **≤ 256 characters** and come back as strings (numbers and booleans are coerced).
* A key that breaks a rule is **dropped and named** in the event's `custom_dropped` array — the
  email is always sent, never blocked by custom data.
* The reserved keys `inbox_tracking`, `campaign_id`, `store_content` and `tags` keep their special
  meaning and are **never echoed back**.
* Custom data is **stripped before delivery** and never reaches the recipient.

Sending over SMTP instead? Use the `X-MC-Custom-*` headers — see [SMTP relay](/guides/smtp-relay#custom-data-on-webhooks).

## Tracking and duplicates

* **Opens** are tracked automatically when your sending domain has a tracking domain configured. There is no per-message parameter on the API; the domain-level **Open Tracking** switch and the `mld-track-opens` header apply to [SMTP relay](/guides/smtp-relay) only.
* **Clicks** are not tracked.
* **Inbox placement** uses `metadata.custom.inbox_tracking` and `metadata.custom.campaign_id`.
* **Duplicates**: reusing a `metadata.messageId` within 24 hours returns success without sending again, so a timed-out request is safe to retry with the same id. SMTP relay has no equivalent.
* **Sensitive content**: set `metadata.custom.store_content` to `false` on OTP or password-reset emails to skip storing the message body. Delivery and tracking are unaffected.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.