
# Send a test email

`POST /v1/email-templates/{family}/{locale}:test-send` · permission `email-templates:write` · Startup plan and above

Renders one version of an email template with the same sample data as [preview](preview.md) and sends it to one address through the normal delivery path, so you can check it in a real inbox. The subject starts with `[TEST] `. The template is validated again before it is sent.

Rules:

- The recipient must be a verified email address of an `active` member of your dashboard account. Any other address is refused, and the address is never repeated in the response or in logs.
- Each environment can send at most 20 test emails per UTC day. The 21st is refused with `429` until the next UTC day (`Retry-After` gives the wait in seconds).
- The suppression list still applies. If the recipient is suppressed, the message is recorded with state `suppressed`, is not delivered and still counts toward the daily limit.
- The environment must send custom templates: a test send is refused when the delivery mode is `platform_default` or sending is restricted or suspended.
- Each accepted test send is written to the management audit log as `email_template.test_sent`, with the family, locale and version but not the recipient.

## Path parameters

| Parameter | Description |
|---|---|
| `family` | Email family, for example `verify_email` or `email_otp` (see [List email templates](list.md)). |
| `locale` | Locale tag, for example `en` or `de`. |

## Headers

| Header | Description |
|---|---|
| `Idempotency-Key` | Optional, 16 to 64 characters of `A-Z a-z 0-9 _ -`. Repeating a request with the same key returns the first message and does not send or count it again. The SDKs and the CLI set it for you. |

## Request

| Field | Type | Description |
|---|---|---|
| `to` | string | Required. The recipient: a verified address of an `active` dashboard member, without a display name. Letter case and surrounding spaces are ignored. |
| `version` | integer | The saved version to send (1 or more). Default: the newest version. |
| `published` | boolean | `true` sends the published version. Cannot be combined with `version`. |

## Response

`200 OK`

```json
{
  "message_id": "0199f3c4-5d6e-7a8b-9c0d-1e2f3a4b5c6d",
  "state": "queued",
  "version": 3,
  "accepted_at": "2026-10-19T09:00:00Z"
}
```

| Field | Description |
|---|---|
| `message_id` | The message's identifier. |
| `state` | `queued` (it will be sent shortly) or `suppressed` (the recipient is on the suppression list). A repeated request returns the first message's current state, which can also be `sent`, `failed` or `expired`. |
| `version` | The version that was sent. |
| `accepted_at` | When the message was accepted. |

## Errors

| HTTP | code | When |
|---|---|---|
| 400 | `unknown_field` | The body has an unknown field or is not a JSON object. |
| 400 | `malformed_request` | The body is not valid JSON, or `Idempotency-Key` is malformed or repeated. |
| 401 | `unauthenticated` | The credential is missing, invalid or revoked. |
| 403 | `insufficient_permissions` | The credential lacks the required permission. |
| 403 | `plan_feature_unavailable` | The environment's plan does not include custom email templates (Startup and above; `required_plan` says which). |
| 404 | `not_found` | The family or locale is unknown, the version does not exist, or `published` is set and nothing is published. |
| 409 | `email_template_not_deliverable` | The environment's delivery mode or sending state does not send custom templates. |
| 422 | `validation_failed` | `to` is missing or is not a plain email address, `version` is below 1, both `version` and `published` were sent (`conflicts_with_published`), or the version no longer passes validation. |
| 422 | `template_forbidden_construct` | The version uses a Liquid construct outside the sandbox. |
| 422 | `test_recipient_not_allowed` | The recipient is not a verified address of an `active` dashboard member. |
| 429 | `rate_limited` | The environment already sent 20 test emails this UTC day, or the API rate limit was exceeded; retry after the `Retry-After` period. |
| 503 | `test_recipient_source_unavailable` | The recipient cannot be checked right now; nothing was sent. Retry after the `Retry-After` period. |
| 503 | `service_unavailable` | A dependency is temporarily unavailable; retry after the `Retry-After` period. |

## Example

```bash
curl -X POST https://api.eu.keenauth.com/v1/email-templates/verify_email/en:test-send \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: test-send-verify-email-en-0001" \
  -d '{"to":"you@example.com","version":3}'
```

CLI: `keenauth email-templates test-send verify_email en --to you@example.com --version 3`

## Personal data

This endpoint carries personal data: dashboard users and account contacts. [Personal data in the KeenAuth APIs](/docs/policies/personal-data) describes what is stored, how long it is kept and how it is deleted. Do not send special categories of personal data (health, biometric data, religion, political opinions, sexual orientation) in any field.
