Skip to content

Send transactional email with the API

How-to guide
For:
Developers
Needs permission:
transactional:sendsensitive
In the app:
Transactional, then Setup

The transactional email API sends password resets, receipts and other one-to-one messages from a published transactional template: one message per call, or a batch of up to 100. Each call answers at once with a message ID, and the message goes out on the Transactional stream through its own route and failover.

If you have none of this yet, the first-send guide walks you through all four in the app.

Transactional Setup lists the HTTP API's paths, the authentication, idempotency, size, rate and scheduling rules, and the three keys that can send: a live send-only key, a test-mode key and an SMTP credential.
  1. Copy your public URL, your workspace slug and the key. The examples read the key from the REPUTECAST_API_KEY environment variable; replace reputecast.example.com and your-workspace with your own values.

  2. Send POST /api/v1/workspaces/<workspace>/transactional/messages with the template ID, the recipient and the template’s variables in data. Send an Idempotency-Key header so a retry never sends twice.

    send.sh
    # REPUTECAST_API_KEY: an API key (rc_live_…) with transactional:send.
    # Replace reputecast.example.com with your public URL and your-workspace with your workspace slug.
    curl --request POST \
    --url https://reputecast.example.com/api/v1/workspaces/your-workspace/transactional/messages \
    --header "Authorization: Bearer $REPUTECAST_API_KEY" \
    --header 'Content-Type: application/json' \
    --header 'Idempotency-Key: password-reset-8f14e45f' \
    --data '{
    "template": "password-reset",
    "to": { "email": "[email protected]", "name": "Sam Rivera" },
    "data": {
    "first_name": "Sam",
    "reset_url": "https://app.example.com/reset/8f14e45f",
    "expires_in": "30 minutes"
    },
    "tags": ["password-reset"]
    }'
  3. Read the answer. ReputeCast answers 202 Accepted once the message is checked, rendered and queued:

    202 Accepted
    { "id": "0192f1c4-7b2e-7c51-9d0a-3f5e8b1a2c44", "status": "queued" }

    status is queued, scheduled (with send_at) or not_delivered (a test-mode key’s message to an address it may not deliver to). Keep the id: the message log, the API and every message.* webhook event use it.

The Node.js example needs Node.js 18 or later (built-in fetch), and the Python example needs the requests package. The operation reference lists every field and answer.

Field Required What it does
template Yes The template ID, up to 64 characters. ReputeCast sends the template’s live (published) version.
to Yes { "email": "…", "name": "…" }. name is optional.
data No The template’s variables. A call without a variable the template marks required gets 422 missing_variables.
from No A verified Transactional sender, as address or Name <address>. Without it: the template’s sender, then the workspace default, then the first verified Transactional sender.
reply_to No An address. Without it, the template’s or the sender’s reply-to applies.
subject, preheader No Replace the template’s subject or preheader for this message. Both accept Liquid.
language No The language version to send, such as es or pt-BR. Without it, the linked contact’s language, then the workspace’s fallback.
cc, bcc No Up to 20 addresses each, in the same shape as to.
attachments No Up to 20 files: filename, content (standard base64), optional content_type (guessed from the file name) and content_id for inline images (cid:).
tags No Up to 10 strings. They come back with every webhook event of the message.
metadata No Up to 20 string pairs (keys up to 100 characters, values up to 500). They come back with every webhook event.
send_at No An RFC 3339 time up to 72 hours ahead. See Schedule a message.

Unknown fields are refused with 422 validation_failed. A recipient who matches a contact shows the message in that contact’s activity; the API never creates contacts.

POST …/transactional/messages/batch takes { "messages": [ … ] } with 1 to 100 messages, each in the same shape as a single send. More than 100 is refused; split larger sends into several calls.

send-batch.sh
# REPUTECAST_API_KEY: an API key (rc_live_…) with transactional:send. Up to 100 messages per call.
# Replace reputecast.example.com with your public URL and your-workspace with your workspace slug.
curl --request POST \
--url https://reputecast.example.com/api/v1/workspaces/your-workspace/transactional/messages/batch \
--header "Authorization: Bearer $REPUTECAST_API_KEY" \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: welcome-2026-10-22-0900' \
--data '{
"messages": [
{
"template": "welcome",
"to": { "email": "[email protected]", "name": "Priya Shah" },
"data": { "first_name": "Priya", "dashboard_url": "https://app.example.com/" }
},
{
"template": "welcome",
"to": { "email": "[email protected]" },
"data": { "dashboard_url": "https://app.example.com/" }
}
]
}'

The batch answers 200 OK with one result per message, in the order sent, and the totals:

200 OK
{
"accepted": 1,
"failed": 1,
"results": [
{ "index": 0, "id": "0192f1c4-8a10-7d3e-b4f2-6c1d9e0a7b55", "status": "queued" },
{
"index": 1,
"status": "error",
"error": {
"status": 422,
"title": "Missing variables",
"code": "missing_variables",
"detail": "welcome v3 needs first_name. Add it to data and call again.",
"missing": ["first_name"]
}
}
]
}

One message’s problem never stops the others. The batch operation reference has the same example in Node.js, Python, Go and PHP.

Send an Idempotency-Key header (up to 255 characters) with every send, for example your own event or order ID.

  • A retry with the same key and the same body within 24 hours gets the first answer again and sends nothing.
  • The same key with a different body within 24 hours gets 409 idempotency_key_reused. Use a new key for a new message.
  • After 24 hours the key is forgotten and can be used again.
  • A batch’s key covers the whole batch; each message in it is recorded under <key>#<index>.

Retry on network errors, 429 and 503 with the same key. Don’t retry 422 answers without changing the request.

Set send_at to send later, up to 72 hours ahead. A later time gets 422 validation_failed (“send_at can be at most 72 hours ahead”); schedule later sends in your own app. A time in the past, or less than a second ahead, sends now. The answer has "status": "scheduled" and the send_at it will use.

A send-only key in test mode starts with rc_test_. It renders and logs every message, but delivers only to the workspace’s members and the key’s allowed test addresses (patterns such as qa+*@example.com). Any other recipient gets "status": "not_delivered" and "test": true, and nothing is sent. Use it in staging and CI.

To check the subject, HTML and text without sending or logging anything, call POST …/transactional/templates/<template>/render with the same to, data and language (render operation).

Errors are RFC 9457 problem documents whose detail says what to fix. The codes a send can get:

  • 422 template_not_found, 422 template_not_published: check the template ID, or publish the template.
  • 422 missing_variables: add the variables listed in missing to data.
  • 422 invalid_recipient: validate addresses in your app before calling.
  • 422 recipient_suppressed: the address bounced, complained or is a spam trap, or the recipient turned off this message’s Optional category. The message is still in the log, with a message.rejected event. Unsubscribes from newsletters don’t stop transactional mail.
  • 422 sender_not_verified: from isn’t a verified Transactional sender, or none exists yet.
  • 422 attachments_too_large: the message is larger than the route’s sending provider accepts; the detail names the limit.
  • 422 template_does_not_render: the live version fails with this data; the detail says why.
  • 403 template_not_allowed: the key is limited to other templates or categories.
  • 409 idempotency_key_reused: see Retry safely with Idempotency-Key.
  • 429 rate_limited: the key’s send rate is used up. Wait for Retry-After seconds and send again with the same idempotency key.
  • 503 no_healthy_route: every sending provider in the route is down. Retry with the same idempotency key.

Each key has its own send rate: 50 messages per second unless the key sets another rate (1–10,000). A batch counts as its number of messages. People who send from the app share 50 per second each. These endpoints don’t count toward the general API limit of 600 requests per minute (Rate limits).

Each server process keeps its own counters, so several web nodes behind a load balancer together allow up to their number times the rate.

  • Open Transactional, then Messages and find the message by recipient, or call GET …/transactional/messages/<id> for its timeline. See Message log and alerts.
  • Subscribe an endpoint to message.delivered, message.bounced and message.failed to hear about each message as it happens (Receive webhooks). Their data carries message_id, template, to, status, provider, your tags and metadata.
  • 401 unauthenticated: send the key as Authorization: Bearer <key>; check that it isn’t revoked or expired.
  • 403 permission_denied: the key lacks transactional:send, or its creator no longer holds it.
  • 422 sender_not_verified on every call: add a sender on a Transactional domain in Settings, then Senders and verify its DNS records.
  • status: not_delivered: you are using a test-mode (rc_test_) key; send with a live key, or add the address to the key’s test addresses.
  • Duplicate messages after timeouts: send an Idempotency-Key, and reuse the same one on every retry.
SettingDefaultHow to change
Messages per batch call1–100Fixed
Scheduling ahead with send_atUp to 72 hoursFixed
Idempotency-Key window24 hours; keys up to 255 charactersFixed
cc, bcc and attachments per messageUp to 20 eachFixed
Message size with attachmentsThe smallest limit among the route's providers: Postmark, Brevo and Azure Communication Services 10 MB, Postal 14, Mailjet 15, SparkPost 20, SMTP and Mailgun 25, SendGrid 30, Amazon SES and Resend 40Sending providers › Routing
Tags and metadata per messageUp to 10 tags (100 characters each); up to 20 metadata pairsFixed
Template IDLowercase letters, digits and single hyphens, up to 64 characters; fixed after the first publishTemplate settings
Rendered content kept30 daysTransactional › Settings; don't keep, 7, 30, 60 or 90 days
Message metadata and events kept90 daysFixed
Bounce rate alert (per template, last hour, at least 20 sent)Above 2%Transactional › Settings › Delivery alerts, 0.1–100%
Failed messages alert (last hour)10 or moreTransactional › Settings › Delivery alerts, 1–100,000
Median delivery time alert (last hour, at least 20 delivered)Above 30 sTransactional › Settings › Delivery alerts, 1–3,600 s
Retries on the primary provider before the fallback takes over10 minutes (at once if the provider is down)Fixed
Soft bounces before an address is suppressed3 within 7 daysFixed
Breaking-change check at publish looks atThe last 7 days of callsFixed
Template import from filesUp to 50 files, 2 MB eachFixed