Skip to content

Transactional email

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

View as Markdown

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.

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"
}'
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.

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 for every route.