# Transactional and test emails

One email to one address: receipts and resets from your app, and tests to your own inbox.

## Send a transactional email

`POST /projects/{project_id}/transactional-emails`

One email to one address, queued at once (201). Sending the same idempotency_key again returns the first email with 200 and sends nothing. Unsubscribes do not stop it; a blocked address does (422 recipient_blocked).

### Parameters

| Name | In | Type | Notes |
| --- | --- | --- | --- |
| `project_id` | path | string | Required |

### Body

Send JSON with every field.

| Field | Type | Notes |
| --- | --- | --- |
| `to` | string | Required |
| `template_id` | string \| null | Required |
| `subject` | string \| null | Required |
| `preview_text` | string \| null | Required |
| `html` | string \| null | Required |
| `markdown` | string \| null | Required |
| `variables` | object of string | Required |
| `reply_to` | string \| null | Required |
| `idempotency_key` | string \| null | Required |

```sh Request
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"
}'
```

### Response

```json 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
}
```

### Errors

| Status | Type | Example message |
| --- | --- | --- |
| 400 | `invalid_data` | Send a JSON request body. |
| 401 | `unauthorized` | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 402 | `plan_limit_reached` | Your plan allows 5,000 contacts. Upgrade the plan to add more. |
| 404 | `project_not_found` | Project not found. |
| 422 | `recipient_blocked` | maya@example.com is blocked in this project: it bounced. |
| 422 | `sender_not_ready` | Verify the domain of your from address before you send email. |
| 422 | `invalid_reference` | No template with that id in this project. |
| 429 | `rate_limited` | Too many requests. Try again in 12 seconds. |

## List transactional emails

`GET /projects/{project_id}/transactional-emails`

Newest first, without each email’s body. Kept for 30 days.

### Parameters

| Name | In | Type | Notes |
| --- | --- | --- | --- |
| `project_id` | path | string | Required |
| `cursor` | query | string \| null | Optional |
| `limit` | query | integer | Optional, default 50 |

```sh Request
curl "https://sendocado.com/api/v1/projects/$PROJECT_ID/transactional-emails" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY"
```

### Response

```json 200
{
  "transactional_emails": [
    {
      "id": "qfoeqh3av90ric7phkqdlmtt7ns26lrw",
      "project_id": "ujzde8gxd6ncf10epf91dhodzdoc9is0",
      "send_id": "v7s6ehogfqrclri1qzj865ufrdl1erbf",
      "to": "maya@example.com",
      "subject": "Your Acme receipt for A-1042",
      "preview_text": null,
      "variables": {
        "order_number": "A-1042",
        "total": "$49.00"
      },
      "reply_to": "billing@acme.example",
      "source_template_id": "29be2u66mr26846p7q9m2i0hz2uep1en",
      "idempotency_key": "order-A-1042-receipt",
      "status": "delivered",
      "created_at": 1790432000000
    }
  ],
  "next_cursor": null
}
```

### Errors

| Status | Type | Example message |
| --- | --- | --- |
| 400 | `invalid_data` | limit: Too big: expected number to be <=200 |
| 401 | `unauthorized` | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | `project_not_found` | Project not found. |
| 429 | `rate_limited` | Too many requests. Try again in 12 seconds. |

## Get a transactional email

`GET /transactional-emails/{transactional_email_id}`

status follows the email: queued, sent, then what the provider reported. Get the send for its events and failures.

### Parameters

| Name | In | Type | Notes |
| --- | --- | --- | --- |
| `transactional_email_id` | path | string | Required |

```sh Request
curl "https://sendocado.com/api/v1/transactional-emails/$TRANSACTIONAL_EMAIL_ID" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY"
```

### Response

```json 200
{
  "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": "delivered",
  "created_at": 1790432000000
}
```

### Errors

| Status | Type | Example message |
| --- | --- | --- |
| 401 | `unauthorized` | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | `transactional_email_not_found` | Transactional email not found. |
| 429 | `rate_limited` | Too many requests. Try again in 12 seconds. |

## Send a test email

`POST /projects/{project_id}/test-emails`

A preview in a real inbox before anyone else gets the email. Merge tags take contact_id’s values when it is set. Test emails are never tracked and have a tighter rate limit.

### Parameters

| Name | In | Type | Notes |
| --- | --- | --- | --- |
| `project_id` | path | string | Required |

### Body

Send JSON with every field.

| Field | Type | Notes |
| --- | --- | --- |
| `to` | string | Required |
| `template_id` | string \| null | Required |
| `subject` | string \| null | Required |
| `preview_text` | string \| null | Required |
| `html` | string \| null | Required |
| `markdown` | string \| null | Required |
| `contact_id` | string \| null | Required |

```sh Request
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"
}'
```

### Response

```json 201
{
  "id": "bqcab69m64p2g158z6tnovmizwdiaeq1",
  "project_id": "ujzde8gxd6ncf10epf91dhodzdoc9is0",
  "send_id": "v7s6ehogfqrclri1qzj865ufrdl1erbf",
  "to": "founder@acme.example",
  "subject": "Welcome to Acme, Maya",
  "status": "queued",
  "created_at": 1790000000000
}
```

### Errors

| Status | Type | Example message |
| --- | --- | --- |
| 400 | `invalid_data` | Send a JSON request body. |
| 401 | `unauthorized` | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 402 | `plan_limit_reached` | Your plan allows 5,000 contacts. Upgrade the plan to add more. |
| 404 | `project_not_found` | Project not found. |
| 422 | `recipient_blocked` | maya@example.com is blocked in this project: it bounced. |
| 422 | `sender_not_ready` | Verify the domain of your from address before you send email. |
| 422 | `invalid_reference` | No template with that id in this project. |
| 429 | `rate_limited` | Too many requests. Try again in 12 seconds. |

## Get a test email

`GET /test-emails/{test_email_id}`

status follows the email, like a transactional email’s.

### Parameters

| Name | In | Type | Notes |
| --- | --- | --- | --- |
| `test_email_id` | path | string | Required |

```sh Request
curl "https://sendocado.com/api/v1/test-emails/$TEST_EMAIL_ID" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY"
```

### Response

```json 200
{
  "id": "bqcab69m64p2g158z6tnovmizwdiaeq1",
  "project_id": "ujzde8gxd6ncf10epf91dhodzdoc9is0",
  "send_id": "v7s6ehogfqrclri1qzj865ufrdl1erbf",
  "to": "founder@acme.example",
  "subject": "Welcome to Acme, Maya",
  "status": "delivered",
  "created_at": 1790000000000
}
```

### Errors

| Status | Type | Example message |
| --- | --- | --- |
| 401 | `unauthorized` | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | `test_email_not_found` | Test email not found. |
| 429 | `rate_limited` | Too many requests. Try again in 12 seconds. |
