Skip to main content

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

FieldTypeNotes
from (required)stringSender address or Name <email>. The domain must be verified.
subject (required)stringMax 998 chars.
html (required)stringHTML body.
to (required)string[]Exactly one recipient. Send one request per independently tracked delivery.
textstringPlain-text alternative. Sends multipart/alternative when present.
replyTostringReply-To address.
tagsobjectKey–value string labels for categorizing the email. See Tags.
headersobjectUp 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.
attachmentsarrayUp 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.

Push instead of poll

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 paramPurpose
statusqueued, sending, sent, delivered, bounced, complained, failed
searchMatch recipient or subject
tagFilter by a tag in key=value form (e.g. campaign=welcome)
from / toISO date range. Uses acceptance time normally, or the matching status-transition time when status is supplied.
limit1–100 (default 20)
cursornext_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:

RecipientResult
success@simulator.amazonses.comDelivered
bounce@simulator.amazonses.comHard bounce (auto-suppressed)
complaint@simulator.amazonses.comComplaint

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.

ResponseerrorMeaning
429CREDITS_EXHAUSTEDMonthly allowance exhausted.
429DAILY_LIMIT_EXCEEDEDDaily 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.