Sending
Send an email
Send a transactional email with POST /emails, including every field, the limits that apply, and what happens after Sooner accepts it.
POST /emails accepts one email, validates it, and queues it for delivery. The response contains the email ID. Delivery happens afterward, and you follow it in the dashboard, with GET /emails/{id}, or through webhooks.
This endpoint works with a sending_access key. See Authentication.
Request
curl -X POST https://api.sooner.dev/emails \
-H "Authorization: Bearer $SOONER_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1042-confirmation" \
-d '{
"from": "Acme <orders@mail.yourcompany.com>",
"to": ["ada@example.com"],
"cc": ["accounts@example.com"],
"reply_to": "support@yourcompany.com",
"subject": "Your order is confirmed",
"html": "<p>Thanks for your order.</p>",
"text": "Thanks for your order.",
"headers": { "X-Entity-Ref-ID": "order-1042" },
"tags": [{ "name": "category", "value": "order_confirmation" }]
}'{ "id": "01jcz8w3f2x9k4m7n6p5q2r1s0" }With the Node SDK:
import { Resend } from "resend";
const resend = new Resend(process.env.SOONER_API_KEY, { baseUrl: "https://api.sooner.dev" });
const { data, error } = await resend.emails.send(
{
from: "Acme <orders@mail.yourcompany.com>",
to: ["ada@example.com"],
subject: "Your order is confirmed",
html: "<p>Thanks for your order.</p>",
},
{ idempotencyKey: "order-1042-confirmation" },
);Parameters
| Field | Type | Required | Description |
|---|---|---|---|
from | string | Yes, unless a template sets it | Sender, as hello@domain.com or Name <hello@domain.com>. 3 to 500 characters. The domain must be verified in the key's project, or be a subdomain of a verified domain. |
to | string or string[] | Yes | Recipient addresses. |
subject | string | Yes, unless a template sets it | 1 to 998 characters. |
html | string | One of html, text or template | HTML body. |
text | string | One of html, text or template | Plain-text body. Send it alongside html for the best compatibility. |
template | object | One of html, text or template | { "id": "...", "variables": { ... } }. See Templates. |
cc | string or string[] | No | Carbon-copy recipients. |
bcc | string or string[] | No | Blind carbon-copy recipients. They are not written into the message headers. |
reply_to | string or string[] | No | Reply-to addresses. replyTo is also accepted. |
headers | object | No | Custom headers, as string keys and string values. See Tags and headers. |
tags | object[] | No | Up to 10 { name, value } pairs. See Tags and headers. |
attachments | object[] | No | Up to 50 files. See Attachments. |
scheduled_at | string | No | ISO 8601 time, or text such as in 1 hour. At most 30 days ahead. scheduledAt is also accepted. See Scheduling. |
topic_id | string | No | Honors the recipient's subscription to this topic. topicId is also accepted. See Topics. |
The Idempotency-Key request header is also supported. See Idempotency.
Limits and rules
- At most 50 recipients in total across
to,ccandbcc. - Each address list is limited to 50 entries, and every address must be valid. Names are allowed, as in
Ada Lovelace <ada@example.com>. - You must provide at least one of
html,textortemplate. - The
fromdomain must be verified in the project that owns the API key. If the key is locked to a domain, thefromaddress must be on that domain. - Every recipient in
to,ccandbccreceives the same message. To send different content to different people in one request, use batch sending.
What happens after you send
The API validates the request, records it, and returns the ID with status 200. At that point the email has status queued, or scheduled if you set scheduled_at.
Sooner then sends the message and records events as they happen. The email status only moves forward through this order:
| Status | Meaning |
|---|---|
queued | Accepted and waiting to be sent. |
scheduled | Accepted and waiting for its scheduled_at time. |
sending | Being handed to the mail network. |
sent | Handed off successfully. |
delivery_delayed | The receiving server has not accepted it yet. Delivery is still being attempted. |
delivered | The receiving server accepted it. |
bounced | The receiving server rejected it. |
complained | The recipient marked it as spam. |
failed | Sooner could not send it. The reason is on the email and in the email.failed event. |
canceled | A scheduled email was canceled. |
A late delivery_delayed event can never move an email back from delivered. failed and canceled are final. Opens and clicks are recorded as events but do not change the status. The last_event field on the email shows the most recent event, for example opened.
Suppressed and opted-out recipients
If every recipient is on your team's suppression list, or the recipient has opted out of the topic_id you set, the request still succeeds and returns an ID. The email is recorded as failed with last_event set to suppressed, and it is not sent. It still counts toward your quota.
If only some recipients are suppressed, the others receive the email, and the suppressed addresses are dropped at send time.
Errors
| Error | When |
|---|---|
invalid_from_address | The from address is invalid, or its domain is not verified in this project. |
restricted_api_key | The key is locked to a different domain. |
validation_error | An invalid field, too many recipients, an invalid address, or an invalid scheduled_at. |
missing_required_field | from or subject is still missing after a template is applied. |
not_found | The template or topic_id does not exist in this project. |
invalid_idempotent_request | The Idempotency-Key was used before with a different body. |
rate_limit_exceeded, daily_quota_exceeded, monthly_quota_exceeded | See Rate limits. |
See Errors for the response format, and the emails API reference to retrieve, list, reschedule and cancel emails.