Mail · API reference
Emails
Send an email, then look it up to see what happened to it. Emails and their events are kept for your plan's log retention (see Plans and limits).
Endpoints
https://services.my-beast.com/mail/api/email/v1
The email object
An email you sent, with a status for every recipient.
Fields
idstringUnique ID, starting with
em_.fromstringThe sender, as you sent it.
toarray of stringsRecipients, as you sent them. Always an array.
ccarray of stringsCopy recipients. Empty if there were none.
bccarray of stringsBlind copy recipients. Empty if there were none.
reply_toarray of stringsReply-to addresses. Empty if there were none.
subjectstringThe subject line.
statusstringqueued,sent,delivered,bounced,complainedorfailed. A summary of the recipients' statuses: see Email statuses.recipientsarray of objectsEvery address the email went to (to, cc and bcc), each once.
emailstringThe address, without a display name.
statusstringThis recipient's status.
tagsarray of objectsThe tags you sent. Empty if there were none.
namestringThe tag's name.
valuestringThe tag's value.
created_atstringWhen we accepted the email (ISO 8601, UTC).
eventsarray of objectsWhat happened, oldest first. Only returned by Retrieve an email.
typestringsent,delivered,bounced,complainedorfailed. Each type happens at most once per recipient.recipientstringThe address this event is about.
reasonstring or nullOn
failedevents:suppressedorexpired. Otherwisenull.messagestring or nullOn
bouncedandfailedevents: a short explanation, often the receiving server's reply. Meant for people, so don't parse it. Otherwisenull.created_atstringWhen it happened (ISO 8601, UTC).
{
"id": "em_01JA2Q8ZK4T9W6",
"from": "Acme <hello@acme.com>",
"to": ["customer@example.com"],
"cc": [],
"bcc": [],
"reply_to": ["support@acme.com"],
"subject": "Your order has shipped",
"status": "delivered",
"recipients": [{ "email": "customer@example.com", "status": "delivered" }],
"tags": [{ "name": "category", "value": "shipping" }],
"created_at": "2026-10-09T09:14:03Z",
"events": [
{
"type": "sent",
"recipient": "customer@example.com",
"reason": null,
"message": null,
"created_at": "2026-10-09T09:14:04Z"
},
{
"type": "delivered",
"recipient": "customer@example.com",
"reason": null,
"message": null,
"created_at": "2026-10-09T09:14:06Z"
}
]
}Email statuses
Every recipient of an email has one of these statuses:
| Status | Meaning |
|---|---|
queued | We've accepted the email and it's waiting to be sent. |
sent | We're delivering it to the recipient's mail server. If that server asks us to try later, we keep trying for up to 24 hours. |
delivered | The recipient's mail server accepted it. We can't see whether it was filed in the inbox or as spam. It can still change to bounced or complained. |
bounced | The recipient's mail server refused it permanently, or the address or its domain doesn't exist. The address is added to your suppression list. |
complained | The recipient marked it as spam. Only some mailbox providers report this. The address is added to your suppression list. |
failed | We didn't deliver it: the address was on your suppression list (reason suppressed), or the recipient's server kept asking us to try later for 24 hours (reason expired). |
With one recipient, the email's status is that recipient's status.
With several, it summarises them. While any recipient has no result yet, it's sent if at least one of those is sent, otherwise queued.
Once every recipient has a result, it's delivered if all were delivered. If not, it's the first of complained, bounced and failed that any recipient has.
A failed event has a reason:
| Reason | Meaning |
|---|---|
suppressed | The address is on your suppression list, so we didn't send to it. |
expired | The recipient's mail server kept asking us to try later, and 24 hours passed. |
Send an email
POST/emails
Accepts an email for sending and returns its ID straight away. Follow what happens next with Retrieve an email or webhooks.
to,ccandbcctogether can hold up to 50 different addresses. An address listed more than once gets the email once.- The whole email, counting
html,textand the decoded attachments, can be up to 10 MB. - Each recipient counts as one email of your plan's monthly allowance. If the recipients don't all fit in what's left this month, nothing is sent.
- Recipients on your suppression list are skipped and don't count towards the allowance. They get a
failedevent with reasonsuppressed. - With a test key the request is checked exactly the same way, and the email is then kept in the test inbox instead of being sent: no verified domain is needed, nothing leaves, nothing counts towards the allowance, and no webhook is sent.
Headers
Idempotency-KeystringoptionalUp to 255 characters, such as a UUID. Retrying with the same key within 24 hours returns the first response instead of sending again. See Idempotency.
Body
fromstringrequiredThe sender: an address, or a name and address like
Acme <hello@acme.com>. Any address at one of your verified sending domains.tostring or array of stringsrequiredOne or more recipients. Each is an address, or a name and address.
ccstring or array of stringsoptionalCopy recipients.
bccstring or array of stringsoptionalBlind copy recipients. Other recipients don't see them.
reply_tostring or array of stringsoptionalWhere replies go, up to 5 addresses.
subjectstringrequiredThe subject line, up to 500 characters.
htmlstringoptionalThe HTML body. Send
html,textor both.textstringoptionalThe plain-text body. We send exactly what you provide and don't make one body from the other.
headersobjectoptionalExtra headers, as name and value strings, such as
List-Unsubscribe. Up to 50. Names use letters, digits and hyphens; values can't contain line breaks. You can't setFrom,Sender,To,Cc,Bcc,Reply-To,Subject,Date,Message-ID,Return-Path,MIME-Version,Content-Type,Content-Transfer-Encoding,DKIM-Signature.tagsarray of objectsoptionalUp to 10 labels of your own. They come back on the email and in webhook events. Names and values use letters, digits,
_and-, up to 256 characters each. Names must be different from each other.namestringrequiredThe tag's name.
valuestringrequiredThe tag's value.
attachmentsarray of objectsoptionalFiles to attach.
filenamestringrequiredThe name the recipient sees, up to 255 characters.
contentstringrequiredThe file's content, base64-encoded.
content_typestringoptionalThe MIME type, such as
application/pdf. Defaultapplication/octet-stream.
Response
200 OK. The email's id, and its status, which is always queued here.
Errors
- 403
domain_not_verifiedThefromdomain isn't verified on your plan. - 409
idempotency_conflictTheIdempotency-Keywas used with a different body, or is still in use. - 422
validation_errorA field is missing or invalid, or a limit above is exceeded. - 429
monthly_limit_reachedThe recipients don't fit in this month's remaining allowance.
Any endpoint can also return the common errors.
Request
curl -X POST https://services.my-beast.com/mail/api/email/v1/emails \
-H "Authorization: Bearer $MYBEAST_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 4f1c2b7e-9d3a-4e8b-a6f0-2c5d8e1b7a93" \
-d '{
"from": "Acme <hello@acme.com>",
"to": ["customer@example.com"],
"reply_to": "support@acme.com",
"subject": "Your order has shipped",
"html": "<p>Your order is on its way.</p>",
"text": "Your order is on its way.",
"tags": [{ "name": "category", "value": "shipping" }],
"attachments": [
{
"filename": "receipt.txt",
"content": "VGhhbmsgeW91IGZvciB5b3VyIG9yZGVyLg==",
"content_type": "text/plain"
}
]
}'Response
{
"id": "em_01JA2Q8ZK4T9W6",
"status": "queued"
}Retrieve an email
GET/emails/{id}
Returns an email with its recipients and events. The bodies, headers and attachments aren't returned. With a test key, an email in its test inbox.
Path parameters
idstringrequiredThe email's ID.
Response
200 OK. The email object, with events.
Errors
- 404
not_foundNo email has this ID, or it's older than your plan's log retention.
Any endpoint can also return the common errors.
Request
curl https://services.my-beast.com/mail/api/email/v1/emails/em_01JA2Q8ZK4T9W6 \
-H "Authorization: Bearer $MYBEAST_API_KEY"Response
{
"id": "em_01JA2Q8ZK4T9W6",
"from": "Acme <hello@acme.com>",
"to": ["customer@example.com"],
"cc": [],
"bcc": [],
"reply_to": ["support@acme.com"],
"subject": "Your order has shipped",
"status": "delivered",
"recipients": [{ "email": "customer@example.com", "status": "delivered" }],
"tags": [{ "name": "category", "value": "shipping" }],
"created_at": "2026-10-09T09:14:03Z",
"events": [
{
"type": "sent",
"recipient": "customer@example.com",
"reason": null,
"message": null,
"created_at": "2026-10-09T09:14:04Z"
},
{
"type": "delivered",
"recipient": "customer@example.com",
"reason": null,
"message": null,
"created_at": "2026-10-09T09:14:06Z"
}
]
}List emails
GET/emails
Returns your emails, newest first, as far back as your plan's log retention. With a test key, the emails in its test inbox.
Query parameters
limitintegeroptionalHow many to return, from 1 to 100. Default 20.
afterstringoptionalThe ID of the last email on the previous page. Leave it out for the first page. See Pagination.
Response
200 OK. A page of email objects, without events.
Errors
- 422
validation_errorlimitis out of range, orafterisn't an email in this list.
Any endpoint can also return the common errors.
Request
curl "https://services.my-beast.com/mail/api/email/v1/emails?limit=20" \
-H "Authorization: Bearer $MYBEAST_API_KEY"Response
{
"data": [
{
"id": "em_01JA2Q8ZK4T9W6",
"from": "Acme <hello@acme.com>",
"to": ["customer@example.com"],
"cc": [],
"bcc": [],
"reply_to": ["support@acme.com"],
"subject": "Your order has shipped",
"status": "delivered",
"recipients": [{ "email": "customer@example.com", "status": "delivered" }],
"tags": [{ "name": "category", "value": "shipping" }],
"created_at": "2026-10-09T09:14:03Z"
}
],
"has_more": true
}