Transactional API
Transactional messages are sent to one recipient at a time, triggered by something happening in your application — a password reset, an order confirmation, a shipment update, a one-time code. They bypass the campaign builder.
Every transactional route authenticates with x-api-key (server-to-server only). The rate limit on each ingest endpoint is 120 requests per minute per IP.
Push
POST /transactional/send
Send a single push to one subscriber. You identify the subscriber either by externalId (preferred — your internal user ID, set via identify) or by their raw token.
curl -X POST https://api.reachbell.com/transactional/send \
-H "x-api-key: rb_live_yourkey" \
-H "Content-Type: application/json" \
-d '{
"externalId": "user_42",
"title": "Your order has shipped",
"body": "Tracking: 1Z999AA10123456784",
"url": "https://example.com/orders/A123",
"iconUrl": "https://cdn.example.com/icon-192.png",
"imageUrl": "https://cdn.example.com/order-hero.jpg",
"badge": "https://cdn.example.com/badge.png"
}'
Response envelope
Transactional responses always come back with 200 OK and a structured envelope. Treat "not delivered" as a normal outcome, not an exception.
{
"success": true,
"subscriberFound": true,
"delivered": true,
"deactivated": false,
"reason": null
}
Common reasons when delivered is false:
reason | What it means |
|---|---|
subscriber_not_found | No subscriber with that externalId or token on this project. |
subscriber_unsubscribed | The user revoked permission. |
frequency_capped | The project's delivery policy blocks another push within the window. |
quiet_hours | Quiet hours are active in the subscriber's timezone. |
provider_error | FCM / APNs / VAPID rejected. Details in the dashboard. |
Error responses are NORMAL, not exceptional. Your code should branch on
deliveredrather than throwing on every miss. Genuine bugs (bad payload, bad auth) still return non-2xx with the standard error envelope.
POST /transactional/email
curl -X POST https://api.reachbell.com/transactional/email \
-H "x-api-key: rb_live_yourkey" \
-H "Content-Type: application/json" \
-d '{
"to": "ada@example.com",
"subject": "Welcome to Acme",
"htmlContent": "<h1>Welcome</h1><p>Glad youre here.</p>",
"textContent": "Welcome. Glad youre here.",
"senderEmail": "hello@acme.com",
"senderName": "Acme Support",
"replyTo": "support@acme.com"
}'
You can also send by template:
curl -X POST https://api.reachbell.com/transactional/email \
-H "x-api-key: rb_live_yourkey" \
-H "Content-Type: application/json" \
-d '{
"to": "ada@example.com",
"templateId": "tmpl_welcome",
"variables": { "name": "Ada", "ctaUrl": "https://acme.com/start" }
}'
Sender domain enforcement
The senderEmail you pass must be on the project's verified email domain (the project.emailConfig.domain field) or a subdomain. If it isn't, the request returns 422 Unprocessable Entity with reason: "sender_domain_not_verified".
Verify your domain in Settings → Email → Domain setup before going live. Sending from an unverified domain blocks the API call outright — it doesn't silently rewrite the sender.
POST /transactional/whatsapp
curl -X POST https://api.reachbell.com/transactional/whatsapp \
-H "x-api-key: rb_live_yourkey" \
-H "Content-Type: application/json" \
-d '{
"to": "+14155551234",
"message": "Your verification code is 482910.",
"templateName": "verification_code"
}'
templateName is required for messages sent outside the 24-hour customer service window. The template must be pre-approved by Meta in the dashboard.
SMS
POST /transactional/sms
curl -X POST https://api.reachbell.com/transactional/sms \
-H "x-api-key: rb_live_yourkey" \
-H "Content-Type: application/json" \
-d '{
"to": "+14155551234",
"message": "Your code is 482910. It expires in 5 minutes.",
"senderId": "Acme"
}'
senderId is the alphanumeric sender shown to the recipient. Carrier rules vary by country — in the US it's ignored in favor of a short code or long code provisioned in the dashboard.
Rate limits
Each ingest endpoint (send, email, whatsapp, sms) is independently capped at 120 requests per minute per IP. Exceeding a cap returns 429 Too Many Requests with a Retry-After header.
If you're sending more than 120/min sustained from one IP, batch via campaigns instead or contact support to raise the cap.
What's next?
- Wire up your backend with the Subscribers API to identify users before sending.
- Set up error handling for non-delivery reasons.
- Automate multi-touch flows with Automations instead of one-off transactional calls.