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

# Your first transactional e-mail in 5 minutes

> A token, a template, one call: an order confirmation sent from your application, and how to know it left.

From an API token to an e-mail in your own inbox: one template in the interface, one call from your code, one read to see it leave.

> [!TIP]
>
> No mailer or verified domain yet? A [test key](https://docs.lekalao.com/developers/sandbox.md) runs
> every step below without sending anything: write to
> `delivered@sink.lekalao.dev` instead of your own address, and the e-mail is
> logged, marked `sent` and reported delivered, webhooks included.

## Before you start

The five minutes start once these three are true. The first two depend on your provider and your DNS, not on Lekalao: they can take longer.

**1. A mailer that sends**

Lekalao sends nothing itself: it hands each e-mail to a provider. **Settings → Mailers** lists the ones your team can use: your own, and the one the service shares with every team when it offers one. One of them must be chosen for **Transactional e-mails**, or be the **Default mailer**.

If the list is empty, **Add a mailer** (SES, Postmark, Mailgun, SendGrid, Brevo, Resend or SMTP), then **Send a test e-mail**: it proves the credentials. See [Mailers](https://docs.lekalao.com/deliverability/mailers.md).

**2. A sender on a verified domain**

On Lekalao's service, the sender address must be on a domain your team added and verified in **Deliverability**. Otherwise the call is refused (`422`), and the message names the domain. DNS records can take from minutes to hours to be seen: do this first. See [Sending domains](https://docs.lekalao.com/deliverability/domains.md).

On an installation of your own, this applies only when its administrator asks for it.

**3. The right role**

Creating a token takes the **editor** or **owner** role in the team.

## 1. Create a token

**Settings → API tokens → New token**. Give it a name ("Online shop"), and under **What it may do**, choose **Read and write**: sending is a write, following the e-mail is a read. Switch on **Test key** to try without sending. **Create**, then copy the token: it is not shown again.

```bash theme={null}
export LEKALAO_TOKEN="12|kJ3v8mQ2xP9…"
```

Keep it on your server. See [Authentication](https://docs.lekalao.com/developers/authentication.md).

## 2. Write the template

**Transactional → Transactional templates → New template**. Name it `order-confirmation`, keep the **HTML** editor, **Create**. Then, on its page:

* **Subject**: `Order {{ order.number }} confirmed`

* the content:

  ```html theme={null}
  <p>Hello {{ customer.name }},</p>
  <p>Your order <strong>{{ order.number }}</strong> is confirmed.</p>
  ```

* **Sender address**: an address on your verified domain. Empty, the team's from **Settings → General** is used.

**Save the e-mail**. The name is what your application sends; the rest can change without touching your code. See [Transactional templates](https://docs.lekalao.com/transactional/templates.md).

## 3. Send it

Put **your own address** in `to`, so that you see the e-mail arrive. An address that does not exist bounces, and goes on your suppression list.

```bash curl theme={null}
curl -X POST https://api.lekalao.com/v1/transactional-mails/send \
  -H "Authorization: Bearer $LEKALAO_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: order-2026-00481-confirmation" \
  -d '{
    "template": "order-confirmation",
    "to": ["you@your-company.example"],
    "variables": {
      "customer": { "name": "Ada" },
      "order": { "number": "2026-00481" }
    }
  }'
```

```javascript Node.js theme={null}
// Node 18 or later: fetch is built in.
const response = await fetch('https://api.lekalao.com/v1/transactional-mails/send', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.LEKALAO_TOKEN}`,
    'Content-Type': 'application/json',
    Accept: 'application/json',
    'Idempotency-Key': 'order-2026-00481-confirmation',
  },
  body: JSON.stringify({
    template: 'order-confirmation',
    to: ['you@your-company.example'],
    variables: { customer: { name: 'Ada' }, order: { number: '2026-00481' } },
  }),
});

console.log(response.status, await response.json());
```

```python Python theme={null}
# Python 3, standard library only.
import json
import os
import urllib.error
import urllib.request

request = urllib.request.Request(
    'https://api.lekalao.com/v1/transactional-mails/send',
    method='POST',
    headers={
        'Authorization': f"Bearer {os.environ['LEKALAO_TOKEN']}",
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        'Idempotency-Key': 'order-2026-00481-confirmation',
    },
    data=json.dumps({
        'template': 'order-confirmation',
        'to': ['you@your-company.example'],
        'variables': {'customer': {'name': 'Ada'}, 'order': {'number': '2026-00481'}},
    }).encode(),
)

try:
    with urllib.request.urlopen(request, timeout=15) as response:
        print(response.status, json.load(response))
except urllib.error.HTTPError as error:  # 4xx and 5xx land here
    print(error.code, json.load(error))
```

```php PHP theme={null}
<?php
// PHP 8, with the curl extension.
$curl = curl_init('https://api.lekalao.com/v1/transactional-mails/send');

curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 15,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('LEKALAO_TOKEN'),
        'Content-Type: application/json',
        'Accept: application/json',
        'Idempotency-Key: order-2026-00481-confirmation',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'template' => 'order-confirmation',
        'to' => ['you@your-company.example'],
        'variables' => ['customer' => ['name' => 'Ada'], 'order' => ['number' => '2026-00481']],
    ]),
]);

$body = json_decode(curl_exec($curl), true);

echo curl_getinfo($curl, CURLINFO_RESPONSE_CODE), ' ', json_encode($body), PHP_EOL;
```

On an installation of your own, the address is `https://your-domain/api/v1/transactional-mails/send`.

The `Idempotency-Key` makes the call safe to repeat: sent twice, it sends one e-mail. Give each e-mail its own key, such as the order number and the action.

## 4. Read the answer

The answer comes as soon as Lekalao has written the e-mail down: `202`, with `status` at `pending`.

```json 202 Accepted theme={null}
{
    "data": {
        "id": "e3b0c442-98fc-4c14-9a1e-2f6b7d8a9c10",
        "template": "order-confirmation",
        "subject": "Order 2026-00481 confirmed",
        "from": "hello@your-company.example",
        "to": ["you@your-company.example"],
        "cc": [],
        "bcc": [],
        "status": "pending",
        "sandbox": false,
        "failure_reason": null,
        "failure_code": null,
        "opens": 0,
        "clicks": 0,
        "sent_at": null,
        "delivered_at": null,
        "bounced_at": null,
        "complained_at": null,
        "created_at": "2026-10-31T10:14:02+00:00"
    }
}
```

`pending` is not a problem: the e-mail leaves from a queue a moment later. Keep its `id`.

## 5. See it leave

Read the e-mail by its `id`:

```bash theme={null}
curl https://api.lekalao.com/v1/transactional-mails/e3b0c442-98fc-4c14-9a1e-2f6b7d8a9c10 \
  -H "Authorization: Bearer $LEKALAO_TOKEN" \
  -H "Accept: application/json"
```

| `status` | Means |
| - | - |
| `pending` | Still on the queue, or waiting to be tried again. Read it again in a few seconds. |
| `sent` | Your provider has it. Look in your inbox. |
| `failed` | Not sent. `failure_code` says why in one word, `failure_reason` in a sentence. |

When the provider fails, Lekalao tries again for up to six hours before it gives up. In your application, let Lekalao tell you rather than ask: the `transactional.*` [webhooks](https://docs.lekalao.com/developers/transactional.md#follow-it-with-webhooks) say when the e-mail is sent, delivered, bounced or failed.

The e-mail is also in **Transactional**, the log, with its content and its opens. See [The log](https://docs.lekalao.com/transactional/log.md).

## When it does not arrive

| You see | Where to look |
| - | - |
| `401` | The token: copied whole, after `Bearer `, and not revoked. |
| `403` | `code` says which: `missing_ability` (a **Read** token), `role_not_allowed` (its owner is now a viewer), `two_factor_required` (the team asks for a second factor its owner has not set up), `team_suspended`. |
| `422` `sender_not_allowed` | The sender is not on a verified domain: **Deliverability**. |
| `422` `validation_error`, `errors.template` | No template of that name in the team. The name is exactly the one in **Transactional templates**. |
| `422` `recipients_suppressed` | Logged, not sent: the address is on the suppression list. |
| `429` `monthly_quota_exceeded` | Logged, not sent: the plan's e-mails of the month are used up. |
| `502` | Logged, not sent: the queue could not take it. Send it again with the same `Idempotency-Key`. |
| `failed`, a while later | `failure_reason` holds the provider's last error. Check the mailer with **Send a test e-mail**. |
| `pending` for minutes | The provider is being retried, or the mailer's rate limit holds the e-mail back. On an installation of your own, **Settings → Maintenance** shows whether the workers run. |
| `sent`, and nothing in the inbox | The spam folder first, then the domain's records. See [Troubleshooting](https://docs.lekalao.com/help/troubleshooting.md#e-mails-land-in-spam). |

## Next

> [card] [Everything about sending](https://docs.lekalao.com/developers/transactional.md)
>
> Without a template, attachments, languages, retries and resends.

> [card] [Webhooks](https://docs.lekalao.com/developers/webhooks.md)
>
> Sent, delivered, bounced, opened: each e-mail's life, signed, in your application.

> [card] [From Laravel](https://docs.lekalao.com/developers/laravel-transport.md)
>
> Keep `Mail::to()->send()`, and send through Lekalao.

> [card] [Errors](https://docs.lekalao.com/errors.md)
>
> Every code, and when to try again.


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