Transactional email API reference by example
Updated
The transactional API is one endpoint. This page covers what it accepts, what it returns, and the failure modes worth handling. For the machine-generated schema, use the interactive reference.
Authentication
POST /api/v1/transactional/send
Authorization: Bearer ik_live_xxxxxxxxxxxxxxxx
Content-Type: application/json
- Keys are created in the dashboard and shown in full once. Only a hash is stored.
- The key must carry the
transactional:sendscope, otherwise you get HTTP 403. - A key can have an expiry date and IP allow or block lists. A request from a blocked or non-allowed IP gets HTTP 403.
- Keep keys on the server. Never ship one in a browser or mobile app bundle.
Request fields
| Field | Required | Notes |
|---|---|---|
| to | yes | A single valid email address. |
| from_email | yes | Must be a registered sender on a verified domain in your workspace. |
| from_name | no | Display name. |
| subject | yes without template_id | With a template it defaults to the template name. |
| html_body | yes without template_id | Merge tags are applied. |
| text_body | no | Sent as the plain-text part. Merge tags are not applied to it, so render it yourself. |
| template_id | yes without html_body | UUID of one of your templates. |
| template_data | no | Object of values for merge tags. All values are converted to strings. |
You must provide html_body or template_id. Merge tags look like , with optional inner spaces. A tag with no matching key is left in the message as-is, so test your data.
Inline HTML
curl https://api.inboxili.com/api/v1/transactional/send \
-H "Authorization: Bearer $INBOXILI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "ada@example.com",
"from_email": "billing@yourdomain.com",
"subject": "Receipt {{order_id}}",
"html_body": "<p>Thanks {{first_name}}, we received your payment.</p>",
"template_data": { "first_name": "Ada", "order_id": "A-1042" }
}'
From a template
curl https://api.inboxili.com/api/v1/transactional/send \
-H "Authorization: Bearer $INBOXILI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "ada@example.com",
"from_email": "hello@yourdomain.com",
"template_id": "00000000-0000-0000-0000-000000000000",
"template_data": { "first_name": "Ada" }
}'
Response
Success is HTTP 200:
{ "status": "sent", "message_id": "0100018f..." }
sent means the delivery provider accepted the message. It does not mean the inbox received it. Use webhooks for delivered, bounced, complained and delayed events.
Errors
Every error has the same shape:
{ "error": { "code": "sender_not_verified", "message": "..." } }
| HTTP | code | Meaning | What to do |
|---|---|---|---|
| 401 | unauthorized | Missing, invalid, revoked or expired key | Fix credentials. Do not retry. |
| 403 | forbidden | Missing scope, or IP not permitted | Fix the key's scope or IP rules. |
| 422 | validation_error | Bad body, for example no html_body or template_id | Fix the request. |
| 422 | sender_not_verified | from_email is not on a verified domain | Verify the domain and sender. |
| 422 | send_failed | The provider rejected the send | Inspect message. Retry only if it looks transient. |
| 429 | rate_limited | Over 120 requests per minute for this key | Back off, then retry. |
Plan sending limits can also reject a request. Treat any 4xx as "do not retry unchanged" unless it is 429.
Retries and duplicates
There is no idempotency key. If your request times out you cannot know whether the email was sent. For OTP and reset emails a duplicate is harmless. For receipts it is not, so record in your own database that an invoice email was sent, and check it before retrying.
Limits to design around
- One recipient per call.
- No attachments, CC, BCC or reply-to fields.
- 120 requests per minute per key.
- HTTPS only. There is no SMTP endpoint.
Next
Pick your stack: Node.js, Python, PHP. Or start from a workflow: OTP email.
Frequently asked questions
- What is the base URL?
- https://api.inboxili.com/api/v1. The send endpoint is POST /transactional/send.
- How do I authenticate?
- Send your API key as a Bearer token in the Authorization header. Keys start with ik_live_ and need the transactional:send scope.
- What happens if I exceed the rate limit?
- The API returns HTTP 429 with error code rate_limited. The limit is 120 requests per minute per API key.
- Is there an official SDK?
- Not yet. The endpoint is a single JSON POST, so a plain HTTP client is enough. The language guides show working examples.
Get an API key
Create a workspace, verify a domain, and make your first API call.