# Transactional email

Receipts, resets and sign-in links from your app, sent at once and safe to retry.

A transactional email is one email to one address that your app asks for: a receipt, a password reset, a sign-in link.

## Send one

Send the content inline, or a `template_id` with `variables` for its `{{ variables.<key> }}` tags.

```sh Send a receipt
curl -X POST "https://sendocado.com/api/v1/projects/$PROJECT_ID/transactional-emails" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "to": "maya@example.com",
  "template_id": "29be2u66mr26846p7q9m2i0hz2uep1en",
  "subject": null,
  "preview_text": null,
  "html": null,
  "markdown": null,
  "variables": {
    "order_number": "A-1042",
    "total": "$49.00"
  },
  "reply_to": "billing@acme.example",
  "idempotency_key": "order-A-1042-receipt"
}'
```

```json Response (201)
{
  "id": "qfoeqh3av90ric7phkqdlmtt7ns26lrw",
  "project_id": "ujzde8gxd6ncf10epf91dhodzdoc9is0",
  "send_id": "v7s6ehogfqrclri1qzj865ufrdl1erbf",
  "to": "maya@example.com",
  "subject": "Your Acme receipt for A-1042",
  "preview_text": null,
  "html": "<p>Thanks for your order, {{ first_name | there }}.</p>\n<p>Order {{ variables.order_number }}: {{ variables.total }}</p>\n",
  "text": "Thanks for your order, {{ first_name | there }}.\n\nOrder {{ variables.order_number }}: {{ variables.total }}",
  "merge_tags": ["first_name", "variables.order_number", "variables.total"],
  "variables": {
    "order_number": "A-1042",
    "total": "$49.00"
  },
  "reply_to": "billing@acme.example",
  "source_template_id": "29be2u66mr26846p7q9m2i0hz2uep1en",
  "idempotency_key": "order-A-1042-receipt",
  "status": "queued",
  "created_at": 1790432000000
}
```

The email is queued at once and sent on its own queue, so it never waits behind a broadcast. Its `status` moves from `queued` to `sent`, then to what the provider reports (`delivered`, `bounced` and so on).

## Retries and idempotency

Send an `idempotency_key`, like your order id, and retry freely: the same key returns the first email with `200` and sends nothing again.

## Who it reaches

- Unsubscribes do not stop transactional email.
- A blocked address does: the answer is `422 recipient_blocked`.
- There is no unsubscribe footer. `reply_to` sets where replies go.

## Test emails

Send any content to your own inbox before anyone else gets it. With a `contact_id`, merge tags use that contact’s values. Test emails are never tracked and are limited to 30 an hour.

```sh Send a test email
curl -X POST "https://sendocado.com/api/v1/projects/$PROJECT_ID/test-emails" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "to": "founder@acme.example",
  "template_id": null,
  "subject": "Welcome to Acme, {{ first_name | there }}",
  "preview_text": null,
  "html": "<p>Hi {{ first_name | there }},</p>\n<p>Thanks for trying Acme. Start with the <a href=\"https://acme.example/setup\">two-minute setup</a>.</p>\n",
  "markdown": null,
  "contact_id": "j8ht9lgmxg9edn581u33xtplpft75v2s"
}'
```

See the [Transactional and test emails reference](https://sendocado.com/docs/api/transactional-emails) for every route.
