Sending emails
Two endpoints send mail. One takes raw HTML; the other renders a stored template.
Both queue the message and return 202 Accepted with an email id.
Send raw HTML
curl -X POST https://api.unlayer.com/v3/emails \
-H "Authorization: Bearer unlayer_sk_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "Acme <hello@mail.example.com>",
"to": ["customer@example.com"],
"subject": "Your receipt",
"html": "<h1>Thanks!</h1><p>Your order shipped.</p>",
"text": "Thanks! Your order shipped."
}'
Request fields
| Field | Type | Notes |
|---|---|---|
from (required) | string | Sender address or Name <email>. The domain must be verified. |
subject (required) | string | Max 998 chars. |
html (required) | string | HTML body. |
to (required) | string[] | Exactly one recipient. Send one request per independently tracked delivery. |
text | string | Plain-text alternative. Sends multipart/alternative when present. |
replyTo | string | Reply-To address. |
tags | object | Key–value string labels for categorizing the email. See Tags. |
headers | object | Up to 9 printable-ASCII X-* headers. Names are at most 126 characters; values at most 995; name plus value at most 996. Unlayer and X-SES-* names are reserved. |
attachments | array | Up to 10 files, 5 MB total. See Attachments. |
Response
{
"data": {
"id": "9b2c2f4e-7e3a-4f1d-9a8b-1c2d3e4f5a6b",
"from": "Acme <hello@mail.example.com>",
"to": ["customer@example.com"],
"subject": "Your receipt",
"status": "queued",
"createdAt": "2026-06-18T12:00:00.000Z"
}
}
202 means queued, not delivered. Track progress with
webhooks or by polling the email.
Send from a stored template
Render a template you saved in the editor (it must have been saved at least once so it has
rendered HTML). The subject, the plain-text part, and the body all support {{variable}}
merge syntax, filled from variables.
curl -X POST https://api.unlayer.com/v3/emails/template \
-H "Authorization: Bearer unlayer_sk_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "Acme <hello@mail.example.com>",
"templateId": "12345",
"to": ["customer@example.com"],
"subject": "Welcome, {{firstName}}",
"variables": { "firstName": "Sam", "plan": "Pro" }
}'
from, to, and templateId are required. If you omit subject it falls back to the
template name. The same one-recipient, replyTo, and attachments rules apply. CC and BCC
are not supported by either send endpoint.
The email uses the template as it was when the request was accepted. Later edits or deletion do not change an already queued email.
Tags
Tags label an email for filtering (GET /v3/emails?tag=campaign=welcome). Up to 10 tags per
email. Keys and values may only contain letters,
numbers, underscores, and hyphens (A–Z a–z 0–9 _ -). Keys are 1–64 characters; values up to 256 characters. Anything outside that
charset (spaces included) is rejected with 400 at acceptance:
{ "tags": { "campaign": "welcome", "order_id": "5512-A" } }
Attachments
Each attachment is base64-encoded inline:
{
"attachments": [
{
"filename": "receipt.pdf",
"content": "JVBERi0xLjQKJ...",
"contentType": "application/pdf"
}
]
}
All three fields are required — filename, content, and contentType. An attachment
without a contentType is rejected with 400.
Limits: up to 10 files and a 5 MB decoded email payload. The limit is the sum of UTF-8 HTML,
UTF-8 plain text (when supplied), and decoded attachment bytes. Base64 transport overhead and
HTTP headers do not count. Whitespace and standard MIME line wrapping in content are removed
before validation. contentType must be an allowed MIME type.
Idempotent sends
Use an Idempotency-Key when retrying a send. Repeating the same key and request within
24 hours returns the same accepted email ID instead of accepting another email. The returned
status may reflect delivery updates since the original request:
curl -X POST https://api.unlayer.com/v3/emails \
-H "Authorization: Bearer unlayer_sk_xxx" \
-H "Idempotency-Key: order-5512-receipt" \
-H "Content-Type: application/json" \
-d '{ "from": "...", "to": ["..."], "subject": "...", "html": "..." }'
If the first request is still in flight, the retry gets 409. Reusing the key with a
different request also gets 409; use a new key for new work. Idempotency is checked before
quota is reserved, so rejected duplicate requests do not consume another daily slot.
Idempotency prevents duplicate acceptance, but delivery cannot be guaranteed exactly once. If a provider accepts a message and its response is lost, a delivery retry can produce a duplicate.
For template sends, retry with the same template ID, variables, and other request fields. Editing or deleting the template after acceptance does not change the accepted email or create another send.
Preview without sending
Render a template to HTML without sending it. Handy for previews and tests.
curl -X POST https://api.unlayer.com/v3/emails/render \
-H "Authorization: Bearer unlayer_sk_xxx" \
-H "Content-Type: application/json" \
-d '{ "templateId": "12345", "variables": { "firstName": "Sam" } }'
{ "data": { "html": "<html>...</html>", "subject": "Welcome email" } }
The preview uses the template's current saved HTML.
Check delivery status
The 202 only means queued. Fetch the current status during the rolling 90-day history
window:
curl https://api.unlayer.com/v3/emails/9b2c2f4e-7e3a-4f1d-9a8b-1c2d3e4f5a6b \
-H "Authorization: Bearer unlayer_sk_xxx"
{
"data": {
"id": "9b2c2f4e-7e3a-4f1d-9a8b-1c2d3e4f5a6b",
"from": "Acme <hello@mail.example.com>",
"to": ["customer@example.com"],
"subject": "Your receipt",
"status": "delivered",
"failureReason": null,
"tags": null,
"createdAt": "2026-06-18T12:00:00.000Z"
}
}
status moves from queued to sent to delivered, or ends at a failure state:
bounced, complained, or failed. When a send fails, failureReason tells
you why (for example, an unverified sender domain) so you can fix it without guessing.
If repeated provider or network errors exhaust the delivery retry budget, the send becomes
failed with an unconfirmed-delivery reason instead of remaining queued indefinitely. A late
provider event can still advance that record if the provider accepted an earlier ambiguous attempt.
For real-time updates, register a webhook rather than polling.
List and inspect emails
In Console, open Sending → Delivery History and use Load older emails to browse beyond the first page. Select a delivery to view its status and event timeline.
List sent emails, newest first, with optional filters and cursor pagination:
curl "https://api.unlayer.com/v3/emails?status=bounced&limit=20" \
-H "Authorization: Bearer unlayer_sk_xxx"
| Query param | Purpose |
|---|---|
status | queued, sending, sent, delivered, bounced, complained, failed |
search | Match recipient or subject |
tag | Filter by a tag in key=value form (e.g. campaign=welcome) |
from / to | ISO date range. Uses acceptance time normally, or the matching status-transition time when status is supplied. |
limit | 1–100 (default 20) |
cursor | next_cursor from the previous page |
To see one email's full history, fetch its event timeline:
curl https://api.unlayer.com/v3/emails/9b2c2f4e-.../events \
-H "Authorization: Bearer unlayer_sk_xxx"
{
"data": [
{
"type": "send",
"timestamp": "2026-06-18T12:00:00.000Z",
"metadata": null
},
{
"type": "delivery",
"timestamp": "2026-06-18T12:00:03.000Z",
"metadata": null
}
]
}
Timeline event types are accepted, send, delivery, bounce, complaint, and
failed. Entries are returned in timestamp order; provider events can arrive late.
Email details and their event timelines are available for 90 days after the email is
accepted. After that window, detail and event requests may return 404. Store verified
webhook events if you need a longer
audit trail.
Testing
Amazon SES provides simulator mailboxes that produce a given outcome without touching real inboxes or your reputation. Send to these to exercise the whole pipeline safely:
| Recipient | Result |
|---|---|
success@simulator.amazonses.com | Delivered |
bounce@simulator.amazonses.com | Hard bounce (auto-suppressed) |
complaint@simulator.amazonses.com | Complaint |
A bounce or complaint here flows through exactly like a real one, so it's the easiest way to verify your suppression and webhook handling.
Compose template values
Console keeps the same retry key for an unchanged Compose request in the same browser tab, including after a reload when session storage is available. Within the 24-hour idempotency window, retrying an accepted request returns the original email even if its saved template has changed. Changing the compose fields starts a new send attempt.
In Console, Compose provides an input for each {{variable}} in the template and
subject. Fill in every required value before sending. Values are escaped in HTML and
inserted as plain text in the subject.
Transactional email
Send supports transactional messages such as password resets, receipts, and requested account activity. Both endpoints use the same delivery and suppression rules. There is no message-purpose selector or built-in unsubscribe flow. Hard bounces, complaints, and manual suppressions block delivery within the project.
Quotas and credits
Each accepted single-recipient email consumes one credit, even if delivery later fails. Retrying the same accepted request with its idempotency key does not consume another credit. Your workspace has a monthly allowance and a daily limit, shared by its projects. The daily limit resets at 00:00 UTC.
View your allowance and available Email Credits packs in Console Billing. Packs increase both monthly and daily capacity. You can reduce a pack only when the remaining monthly allowance covers credits already used in the current period. If Send access is disabled, you can remove an existing pack even when prior usage exceeds the remaining allowance. If a purchase is interrupted, refresh Billing to check whether it completed before retrying.
Successful responses include X-Credits-* headers when quota information is available.
These headers are advisory and may be omitted; the response status and body tell you
whether your email was accepted.
| Response | error | Meaning |
|---|---|---|
429 | CREDITS_EXHAUSTED | Monthly allowance exhausted. |
429 | DAILY_LIMIT_EXCEEDED | Daily limit reached; resets at 00:00 UTC. |
Statistics distinguish accepted, sent, and failed emails and may briefly lag behind sending. A failed email with an unconfirmed delivery can later receive a provider confirmation, so failed and sent counts can overlap. Use email details or webhooks to check an individual send.