Send Email
Queue a transactional message, singly or in a batch.
/emailsAPI key or sessionRequest body
| Field | Type | Required | Notes |
|---|---|---|---|
| from | string | yes | Sender, as "Acme <hello@example.com>". Must be on a verified domain. |
| to | string | string[] | yes | One or more recipients. |
| subject | string | yes* | Not required when using a template that supplies one. |
| html | string | no* | HTML body. |
| text | string | no* | Plain-text body. Send both where you can. |
| template | object | no* | { id, variables }. Mutually exclusive with html and text. |
| cc / bcc | string | string[] | no | Count as recipients for billing and for the 50 cap. |
| reply_to | string | string[] | no | Where replies should go. |
| scheduled_at | string | no | ISO 8601 timestamp in the future. |
| tags | object | array | no | Arbitrary labels, returned with the message. |
| headers | object | no | Custom headers. |
| topic_id | string | no | Only send to contacts subscribed to this topic. |
| attachments | array | no | { filename, content (base64), content_type }. |
*At least one of html, text or template is required.
Example
curl -X POST https://api.rovela.dev/emails \
-H "Authorization: Bearer re_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"from": "Acme <hello@yourdomain.com>",
"to": ["alice@example.com", "bob@example.com"],
"subject": "Your invoice is ready",
"html": "<p>Hello {{name}}</p>"
}'{"object": "email", "id": "9f1c2e40-..."}Recipients and billing
A message may address at most 50 recipients across to, cc and bcc combined. That is the transport's limit, not a plan limit, and it is checked before the request is accepted — so nothing is charged for a message the mail server would have refused.
cc and bcc count exactly the same as to — every address is a separate delivery. A batch of 100 messages to two recipients each costs 200.Idempotency
Send an Idempotency-Key header (up to 256 characters) and a retry of the same request within 24 hours returns the original response instead of sending again. Use it for anything driven by a queue or a webhook, where "did that already happen?" is a question you will otherwise have to answer yourself.
Scheduling
A future scheduled_at queues the message and returns immediately; the id you get back is the message's, so you can read or cancel it before it goes out. POST /emails/:id/cancel cancels a message that has not been sent yet.
Batch
/emails/batchAPI key or sessionUp to 100 messages in one request, each an object with the same schema. Every entry is processed independently, so one bad message does not discard the others, and the response lists an id per entry in order.
Two fields are rejected rather than ignored: template and topic_id. Both refusals are deliberate — a batch that silently dropped topic_id would mail people who had unsubscribed from that topic, and one that dropped template would send a message with no body. Send those through POST /emails.
curl -X POST https://api.rovela.dev/emails/batch \
-H "Authorization: Bearer re_your_api_key" \
-H "Content-Type: application/json" \
-d '[
{"from": "hello@yourdomain.com", "to": "a@example.com", "subject": "Hi A", "text": "Hello A"},
{"from": "hello@yourdomain.com", "to": "b@example.com", "subject": "Hi B", "text": "Hello B"}
]'{
"object": "list",
"data": [
{"object": "email", "id": "<uuid1>"},
{"object": "email", "id": "<uuid2>"}
]
}Reading a message
/emails/:idAPI key or sessionThe current state of a message. A 200 from the send endpoint means accepted for delivery, not delivered — this is where you find out what happened after that.
Events
/emails/:id/eventsAPI key or sessionThe timeline for one message: accepted, delivered, opened, clicked, bounced or failed, with a timestamp on each. Useful for debugging a single message; for anything systematic, subscribe to a webhook instead of polling this.
Errors
400— a missing required field, a base64 attachment that does not decode, afromdomain not belonging to your organization, atemplatethat does not exist, or more than 50 recipients on one message.401— missing or invalid credential.429— rate limit exceeded. Each message in a batch counts individually, so a large batch can trip this partway through.