Emails
Send, batch, retrieve, list, reschedule and cancel emails.
Send an email
POST /emails
Validates, stores and queues an email. Works with full_access and sending_access keys. See Send emails for a guide.
Headers
| Name | Type | Required | Description |
|---|---|---|---|
Idempotency-Key | string | No | Up to 256 characters. See Idempotency. |
Body parameters
| Name | Type | Required | Description |
|---|---|---|---|
from | string | Yes* | Sender, [email protected] or Name <[email protected]>, on a verified domain. *Optional when the template has a sender. |
to | string | string[] | Yes | Recipients. Up to 50 recipients across to, cc and bcc. |
subject | string | Yes* | A single line. *Optional when the template has a subject. |
html | string | No | HTML body. html or text is required unless you use template. |
text | string | No | Plain-text body. |
cc | string | string[] | No | Cc recipients. |
bcc | string | string[] | No | Bcc recipients. |
reply_to | string | string[] | No | Reply-to addresses. replyTo is also accepted. |
headers | object | No | Custom headers, as { "Name": "value" }. Reserved headers are refused. |
tags | object[] | No | { "name", "value" } pairs. ASCII letters, numbers, _ and -, up to 256 characters each. |
attachments | object[] | No | { "filename", "content", "content_type" } with base64 content. Up to 40 MB in total. |
scheduled_at | string | No | ISO 8601 timestamp or a phrase like in 1 hour or tomorrow at 9am (UTC), up to 30 days ahead. scheduledAt is also accepted. |
template | object | No | { "id": "<template id or alias>", "variables": { ... } }. Uses the published version of a template. Cannot be combined with html or text. |
curl -X POST https://api.usend.et/emails \
-H "Authorization: Bearer us_123456789" \
-H "Content-Type: application/json" \
-d '{
"from": "Acme <[email protected]>",
"to": ["[email protected]"],
"subject": "Hello world",
"html": "<p>It works!</p>",
"tags": [{ "name": "category", "value": "welcome" }]
}'const { data, error } = await usend.emails.send({
from: 'Acme <[email protected]>',
to: ['[email protected]'],
subject: 'Hello world',
html: '<p>It works!</p>',
tags: [{ name: 'category', value: 'welcome' }],
});email = usend.emails.send({
"from": "Acme <[email protected]>",
"to": ["[email protected]"],
"subject": "Hello world",
"html": "<p>It works!</p>",
"tags": [{"name": "category", "value": "welcome"}],
}){ "id": "4ef9a417-02e9-4d39-ad75-9611e0fcc33c" }Send a batch of emails
POST /emails/batch
Sends up to 100 emails in one request. The body is an array of email objects with the same fields as POST /emails, except attachments and scheduled_at. Works with full_access and sending_access keys. See Batch sending.
Headers
| Name | Type | Required | Description |
|---|---|---|---|
x-batch-validation | string | No | strict (default): one invalid email rejects the batch. permissive: valid emails are sent and invalid ones are listed in errors. |
Idempotency-Key | string | No | Replays the first response for 24 hours. |
curl -X POST https://api.usend.et/emails/batch \
-H "Authorization: Bearer us_123456789" \
-H "Content-Type: application/json" \
-d '[
{ "from": "Acme <[email protected]>", "to": ["[email protected]"], "subject": "Hi Ann", "text": "Hello" },
{ "from": "Acme <[email protected]>", "to": ["[email protected]"], "subject": "Hi Ben", "text": "Hello" }
]'const { data, error } = await usend.batch.send([
{ from: 'Acme <[email protected]>', to: ['[email protected]'], subject: 'Hi Ann', text: 'Hello' },
{ from: 'Acme <[email protected]>', to: ['[email protected]'], subject: 'Hi Ben', text: 'Hello' },
]);result = usend.batch.send([
{"from": "Acme <[email protected]>", "to": ["[email protected]"], "subject": "Hi Ann", "text": "Hello"},
{"from": "Acme <[email protected]>", "to": ["[email protected]"], "subject": "Hi Ben", "text": "Hello"},
]){
"data": [
{ "id": "4ef9a417-02e9-4d39-ad75-9611e0fcc33c" },
{ "id": "9a2c5f10-7b1e-4d0f-8a6b-2f4c1e9d3b77" }
]
}With x-batch-validation: permissive, the response also has "errors": [{ "index": 1, "message": "..." }].
Retrieve an email
GET /emails/{id}
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The email ID (path). |
curl https://api.usend.et/emails/4ef9a417-02e9-4d39-ad75-9611e0fcc33c \
-H "Authorization: Bearer us_123456789"const { data, error } = await usend.emails.get('4ef9a417-02e9-4d39-ad75-9611e0fcc33c');email = usend.emails.get("4ef9a417-02e9-4d39-ad75-9611e0fcc33c"){
"object": "email",
"id": "4ef9a417-02e9-4d39-ad75-9611e0fcc33c",
"from": "Acme <[email protected]>",
"to": ["[email protected]"],
"cc": [],
"bcc": [],
"reply_to": [],
"subject": "Hello world",
"html": "<p>It works!</p>",
"text": "",
"tags": [{ "name": "category", "value": "welcome" }],
"created_at": "2026-10-11T09:12:43.120Z",
"scheduled_at": null,
"last_event": "delivered",
"broadcast_id": null
}last_event is one of queued, scheduled, sent, delivered, delivery_delayed, bounced, complained, opened, clicked, failed or canceled. When sending failed, or some recipients were skipped, an error field explains why. html and text are emptied after your plan's data retention period.
List emails
GET /emails
Returns sent emails, newest first, without html and text. Paginated.
| Name | Type | Required | Description |
|---|---|---|---|
limit | integer | No | 1 to 100. Default 50. |
after | string | No | Return emails older than this email ID. |
before | string | No | Return emails newer than this email ID. |
curl "https://api.usend.et/emails?limit=2" \
-H "Authorization: Bearer us_123456789"{
"object": "list",
"data": [
{
"object": "email",
"id": "4ef9a417-02e9-4d39-ad75-9611e0fcc33c",
"from": "Acme <[email protected]>",
"to": ["[email protected]"],
"cc": [],
"bcc": [],
"reply_to": [],
"subject": "Hello world",
"tags": [],
"created_at": "2026-10-11T09:12:43.120Z",
"scheduled_at": null,
"last_event": "delivered",
"broadcast_id": null
}
],
"has_more": true
}List email events
GET /emails/{id}/events
Returns the full event history of an email, oldest first.
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The email ID (path). |
curl https://api.usend.et/emails/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/events \
-H "Authorization: Bearer us_123456789"{
"object": "list",
"data": [
{ "id": "0f6a1f52-6b3e-4d1a-9a3c-5e2b8d7c4f01", "type": "email.queued", "recipient": "", "payload": {}, "created_at": "2026-10-11T09:12:43.130Z" },
{ "id": "5c2d8e7a-1b4f-4a6e-8d3c-9f0e1a2b3c4d", "type": "email.sent", "recipient": "", "payload": {}, "created_at": "2026-10-11T09:12:44.512Z" },
{
"id": "7e9f0a1b-2c3d-4e5f-8a9b-0c1d2e3f4a5b",
"type": "email.delivered",
"recipient": "[email protected]",
"payload": { "delivery": { "response": "250 2.0.0 OK" } },
"created_at": "2026-10-11T09:12:47.118Z"
}
]
}type is one of the email event types. payload holds the event-specific details (bounce, delivery, open, click...). Events are kept for 90 days.
Update a scheduled email
PATCH /emails/{id}
Changes the send time of an email that is still scheduled.
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The email ID (path). |
scheduled_at | string | Yes | The new time: ISO 8601 or a phrase like in 1 hour, in the future and at most 30 days ahead. |
curl -X PATCH https://api.usend.et/emails/4ef9a417-02e9-4d39-ad75-9611e0fcc33c \
-H "Authorization: Bearer us_123456789" \
-H "Content-Type: application/json" \
-d '{ "scheduled_at": "2026-10-12T09:00:00Z" }'const { data, error } = await usend.emails.update({
id: '4ef9a417-02e9-4d39-ad75-9611e0fcc33c',
scheduled_at: '2026-10-12T09:00:00Z',
});usend.emails.update("4ef9a417-02e9-4d39-ad75-9611e0fcc33c", "2026-10-12T09:00:00Z"){ "object": "email", "id": "4ef9a417-02e9-4d39-ad75-9611e0fcc33c" }Fails with 422 if the email is no longer scheduled.
Cancel a scheduled email
POST /emails/{id}/cancel
Cancels an email that is still scheduled. Records an email.canceled event.
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The email ID (path). |
curl -X POST https://api.usend.et/emails/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/cancel \
-H "Authorization: Bearer us_123456789"const { data, error } = await usend.emails.cancel('4ef9a417-02e9-4d39-ad75-9611e0fcc33c');usend.emails.cancel("4ef9a417-02e9-4d39-ad75-9611e0fcc33c"){ "object": "email", "id": "4ef9a417-02e9-4d39-ad75-9611e0fcc33c" }List attachments
GET /emails/{id}/attachments
Lists the attachments of a sent email with signed download links. See Attachments.
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The email ID (path). |
curl https://api.usend.et/emails/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/attachments \
-H "Authorization: Bearer us_123456789"{
"object": "list",
"data": [
{
"object": "attachment",
"id": "2a0c9ce0-3112-4728-976e-47ddcd16a318",
"filename": "invoice-1042.pdf",
"content_type": "application/pdf",
"content_disposition": "attachment",
"size": 48213,
"download_url": "https://api.usend.et/attachments/eyJrIjoib3V0Ii...",
"expires_at": "2026-10-11T10:12:43Z"
}
]
}download_url needs no API key and expires after one hour.
Retrieve an attachment
GET /emails/{id}/attachments/{attachment_id}
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The email ID (path). |
attachment_id | string | Yes | The attachment ID (path). |
curl https://api.usend.et/emails/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/attachments/2a0c9ce0-3112-4728-976e-47ddcd16a318 \
-H "Authorization: Bearer us_123456789"Returns one attachment object, as in the list above.