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

Shell
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" }]
  }'
JSON
{ "id": "01jcz8w3f2x9k4m7n6p5q2r1s0" }

With the Node SDK:

TypeScript
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

FieldTypeRequiredDescription
fromstringYes, unless a template sets itSender, 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.
tostring or string[]YesRecipient addresses.
subjectstringYes, unless a template sets it1 to 998 characters.
htmlstringOne of html, text or templateHTML body.
textstringOne of html, text or templatePlain-text body. Send it alongside html for the best compatibility.
templateobjectOne of html, text or template{ "id": "...", "variables": { ... } }. See Templates.
ccstring or string[]NoCarbon-copy recipients.
bccstring or string[]NoBlind carbon-copy recipients. They are not written into the message headers.
reply_tostring or string[]NoReply-to addresses. replyTo is also accepted.
headersobjectNoCustom headers, as string keys and string values. See Tags and headers.
tagsobject[]NoUp to 10 { name, value } pairs. See Tags and headers.
attachmentsobject[]NoUp to 50 files. See Attachments.
scheduled_atstringNoISO 8601 time, or text such as in 1 hour. At most 30 days ahead. scheduledAt is also accepted. See Scheduling.
topic_idstringNoHonors 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, cc and bcc.
  • 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, text or template.
  • The from domain must be verified in the project that owns the API key. If the key is locked to a domain, the from address must be on that domain.
  • Every recipient in to, cc and bcc receives 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:

StatusMeaning
queuedAccepted and waiting to be sent.
scheduledAccepted and waiting for its scheduled_at time.
sendingBeing handed to the mail network.
sentHanded off successfully.
delivery_delayedThe receiving server has not accepted it yet. Delivery is still being attempted.
deliveredThe receiving server accepted it.
bouncedThe receiving server rejected it.
complainedThe recipient marked it as spam.
failedSooner could not send it. The reason is on the email and in the email.failed event.
canceledA 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

ErrorWhen
invalid_from_addressThe from address is invalid, or its domain is not verified in this project.
restricted_api_keyThe key is locked to a different domain.
validation_errorAn invalid field, too many recipients, an invalid address, or an invalid scheduled_at.
missing_required_fieldfrom or subject is still missing after a template is applied.
not_foundThe template or topic_id does not exist in this project.
invalid_idempotent_requestThe Idempotency-Key was used before with a different body.
rate_limit_exceeded, daily_quota_exceeded, monthly_quota_exceededSee Rate limits.

See Errors for the response format, and the emails API reference to retrieve, list, reschedule and cancel emails.