Emails
Send, batch send, schedule, retrieve, reschedule and cancel emails.
Send an email
/api/v1/emailsAccepts one email and returns its id. The email is queued and handed to the sending server for your domain's region; follow its progress with retrieve, events or webhooks. Works with full_access and sending_access keys.
Body
fromstringrequired- Sender address, optionally with a display name:
Acme <[email protected]>. Must be on a verified domain of the workspace (or[email protected]for tests to workspace members). Optional when atemplatedefines it. tostring | string[]required- Recipient address(es). At least one; at most 50 recipients across
to,ccandbcc. subjectstringrequired- Subject line (no line breaks). Optional when a
templatedefines it. htmlstring- HTML body. Provide
html,textor both (or atemplate). When onlyhtmlis given, a plain-text part is generated from it. textstring- Plain-text body.
ccstring | string[]- Cc recipients.
bccstring | string[]- Bcc recipients (not shown in the message headers).
reply_tostring | string[]- Reply-To address(es). Multiple addresses are joined into one
Reply-Toheader. scheduled_atstring (ISO 8601)- Send later, e.g.
2026-10-01T09:00:00Z. Must be in the future and at most 30 days ahead. The email is stored withlast_event: "scheduled"until then and can be rescheduled or canceled. tags{ name, value }[]- Up to 50 tags for your own bookkeeping, echoed in webhook payloads. Names and values: ASCII letters, digits,
_and-, at most 256 characters. An object{ name: value }is accepted too. headersobject- Custom headers. Only
List-Unsubscribe,List-Unsubscribe-Post(valueList-Unsubscribe=One-Click, and only with an httpsList-UnsubscribeURL) andX-Entity-Ref-IDare passed through to the message (values up to 900 characters, no line breaks); other headers are not added to the email. template{ id, variables }- Render a published template instead of
html/text.idis the template id or its alias;variablesis an object of values. See Sending with a template. attachments—- Not supported yet: any non-empty value is rejected with
422 attachments_not_supported.
Headers
Idempotency-Keystring- Optional, 1–256 characters. Retries with the same key return the first response. See Idempotency.
# Send an email
curl -X POST 'https://api.bytesms.com/api/v1/emails' \
-H 'Authorization: Bearer tp_live_xxxxxxxxx' \
-H 'Content-Type: application/json' \
-d '{"from":"Acme <[email protected]>","to":["[email protected]"],"subject":"Your order has shipped","html":"<p>Your order <strong>#4711</strong> is on its way.</p>","reply_to":"[email protected]","tags":[{"name":"category","value":"shipping"}]}'{
"id": "cmg4k2x7a0001l8v9h3q2d5rt"
}Sending with a template
With template, the published version of the template is rendered server-side. from, subject and reply_to in the request override the template's; html/textcan't be combined with a template. Variable values are HTML-escaped in the HTML body. A variable the template uses without a fallback value must be provided, otherwise the send fails with 422; an unpublished template is 422 too.
# Send with a template
curl -X POST 'https://api.bytesms.com/api/v1/emails' \
-H 'Authorization: Bearer tp_live_xxxxxxxxx' \
-H 'Content-Type: application/json' \
-d '{"from":"Acme <[email protected]>","to":"[email protected]","template":{"id":"order-shipped","variables":{"first_name":"Jane","order_id":4711}}}'Who you can send from and to
frommust be on a domain with statusverifiedin this workspace — otherwise403 invalid_from_address(or403 validation_errorif the workspace has no verified domain yet).[email protected]may only send to members of the workspace and to@test.bytesms.comtest addresses.- A domain-scoped API key may only send from its domain (
403 restricted_api_key). - Recipients on your suppression list (previous hard bounces and complaints) are skipped; if every recipient is suppressed, the email ends
failed.
Send a batch
/api/v1/emails/batchSend up to 100 emails in one request. The body is a JSON array of email objects with the same fields as send, except scheduled_at (not supported in batches). Validation, sending rules and the quota check are all-or-nothing: either every email is accepted or none is. Supports Idempotency-Key.
# Send a batch
curl -X POST 'https://api.bytesms.com/api/v1/emails/batch' \
-H 'Authorization: Bearer tp_live_xxxxxxxxx' \
-H 'Content-Type: application/json' \
-d '[{"from":"Acme <[email protected]>","to":["[email protected]"],"subject":"Hi A","text":"Hello A"},{"from":"Acme <[email protected]>","to":["[email protected]"],"subject":"Hi B","text":"Hello B"}]'{
"data": [
{
"id": "cmg4k2x7a0002l8v9w1b8n4qe"
},
{
"id": "cmg4k2x7a0003l8v9c7m0p2ya"
}
]
}Retrieve an email
/api/v1/emails/{email_id}Returns the email with its current last_event and the full event history. Requires a full_access key.
# Retrieve an email curl -X GET 'https://api.bytesms.com/api/v1/emails/cmg4k2x7a0001l8v9h3q2d5rt' \ -H 'Authorization: Bearer tp_live_xxxxxxxxx'
{
"object": "email",
"id": "cmg4k2x7a0001l8v9h3q2d5rt",
"from": "Acme <[email protected]>",
"to": [
"[email protected]"
],
"cc": [],
"bcc": [],
"reply_to": "[email protected]",
"subject": "Your order has shipped",
"html": "<p>Your order <strong>#4711</strong> is on its way.</p>",
"text": "Your order #4711 is on its way.",
"created_at": "2026-09-28T08:15:02.114Z",
"scheduled_at": null,
"last_event": "delivered",
"events": [
{
"type": "email.sent",
"created_at": "2026-09-28T08:15:03.020Z",
"data": {
"email_id": "cmg4k2x7a0001l8v9h3q2d5rt",
"from": "Acme <[email protected]>",
"to": [
"[email protected]"
],
"subject": "Your order has shipped"
}
},
{
"type": "email.delivered",
"created_at": "2026-09-28T08:15:05.480Z",
"data": {
"email_id": "cmg4k2x7a0001l8v9h3q2d5rt",
"recipients": [
"[email protected]"
],
"dsn": "2.0.0"
}
}
]
}| last_event | Meaning |
|---|---|
queued | Accepted, waiting to be handed to the sending server. |
scheduled | Waiting for its scheduled_at. |
sent | Accepted by our sending server for delivery. |
delivered | The recipient's mail server accepted it (not necessarily the inbox). |
bounced | The recipient's mail server permanently rejected it. |
complained | The recipient marked it as spam. |
failed | Not sent (e.g. suppressed recipient, sending error). |
canceled | A scheduled email that was canceled. |
List emails
/api/v1/emailsNewest first. Items have the same fields as retrieve, without html, text and events.
Query
limitintegerdefault20- 1–100.
afterstring- An email id: return older emails after it (next page).
beforestring- An email id: return newer emails before it. Don't combine with
after.
# List emails curl -X GET 'https://api.bytesms.com/api/v1/emails?limit=20' \ -H 'Authorization: Bearer tp_live_xxxxxxxxx'
{
"object": "list",
"has_more": true,
"data": [
{
"object": "email",
"id": "cmg4k2x7a0001l8v9h3q2d5rt",
"...": "..."
}
]
}List an email's events
/api/v1/emails/{email_id}/eventsEvery recorded event, oldest first: email.sent, email.delivered, email.delivery_delayed, email.bounced, email.complained, email.failed. data holds event details such as the bounce reason.
{
"data": [
{
"type": "email.sent",
"created_at": "2026-09-28T08:15:03.020Z",
"data": {
"email_id": "cmg4k2x7a0001l8v9h3q2d5rt",
"from": "Acme <[email protected]>",
"to": [
"[email protected]"
],
"subject": "Hello"
}
},
{
"type": "email.bounced",
"created_at": "2026-09-28T08:15:06.901Z",
"data": {
"email_id": "cmg4k2x7a0001l8v9h3q2d5rt",
"bounce": {
"type": "Permanent",
"subType": "5.1.1",
"reason": "550 5.1.1 User unknown"
},
"recipients": [
"[email protected]"
]
}
}
]
}Reschedule an email
/api/v1/emails/{email_id}Change the scheduled_at of an email that is still scheduled; any other state is 422 validation_error.
Body
scheduled_atstring (ISO 8601)required- New send time, in the future and within 30 days.
# Reschedule
curl -X PATCH 'https://api.bytesms.com/api/v1/emails/cmg4k2x7a0001l8v9h3q2d5rt' \
-H 'Authorization: Bearer tp_live_xxxxxxxxx' \
-H 'Content-Type: application/json' \
-d '{"scheduled_at":"2026-10-01T09:00:00Z"}'{
"object": "email",
"id": "cmg4k2x7a0001l8v9h3q2d5rt"
}Cancel a scheduled email
/api/v1/emails/{email_id}/cancelCancels an email that is still scheduled. Its last_event becomes canceled and its quota is released.
# Cancel curl -X POST 'https://api.bytesms.com/api/v1/emails/cmg4k2x7a0001l8v9h3q2d5rt/cancel' \ -H 'Authorization: Bearer tp_live_xxxxxxxxx'
{
"object": "email",
"id": "cmg4k2x7a0001l8v9h3q2d5rt"
}Legacy endpoint
POST /api/v1/emails/send accepts the same body but returns the full internal message record and a different error format. It is kept for existing integrations; use POST /api/v1/emails for new code.