Sending
More ways to use this page
Send one message
POST/v1/
TypeScript sendora
Python sendora
Key sk_… a server key
Accepts one message for delivery and answers at once with its id. Delivery, deferral, bounce and complaint arrive later as events on the message and as webhooks. With an Idempotency-Key, a retry within 24 hours gets the first answer again, marked by the Idempotent-Replayed header, and sends nothing twice.
Parameters
idempotency-keyin headerstringAny text of 1 to 255 printable characters that identifies this request; the same key with a different body is refused.
Request body
fromone of 2requiredThe sender. Its domain must be a verified sending domain of the server.
- string (email)
emailstring (email)requirednamestring (at least 1, at most 200 characters) or nullA display name of up to 200 characters.
streamIdstring (uuid)The stream of this server the message goes on; the default transactional stream when absent.
toarray of one of 2, at least 1requiredRecipients; at most 50 across to, cc and bcc.
- string (email)
emailstring (email)requirednamestring (at least 1, at most 200 characters) or nullA display name of up to 200 characters.
ccarray of one of 2Copied recipients, shown in the message.
- string (email)
emailstring (email)requirednamestring (at least 1, at most 200 characters) or nullA display name of up to 200 characters.
bccarray of one of 2Blind-copied recipients.
- string (email)
emailstring (email)requirednamestring (at least 1, at most 200 characters) or nullA display name of up to 200 characters.
subjectstring (at least 1, at most 998 characters)requiredThe subject line.
textstring (at least 1 character)The plain-text part. At least one of text and html is required.
htmlstring (at least 1 character)The HTML part.
headersmap of string to stringdefault {}Custom headers by name, at most 20. Sendora writes these itself, so a message may not set them: From, Sender, To, Cc, Bcc, Subject, Date, Message-ID, MIME-Version, Return-Path, Received, Delivered-To, DKIM-Signature, DomainKey-Signature, Authentication-Results, Received-SPF, and any name that starts with Content-, Resent-, ARC- or X-Kumo.
attachmentsarray of object, at most 20At most 20. A message with its attachments encoded may be at most 10 MB.
namestringrequiredcontentstring (base64)requiredcontentTypestringcontentIdstring or null
tagstring or nullA label of up to 100 characters, returned with the message and its events.
metadatamap of string to stringdefault {}Up to 20 key-value pairs of your own, returned with the message and its events.
{
"from": {
"email": "no-reply@example.se",
"name": "Example AB"
},
"to": [
"anna@example.com"
],
"subject": "Your invoice for September",
"text": "Hi Anna, your invoice is attached.",
"attachments": [
{
"name": "invoice.pdf",
"content": "JVBERi0xLjcK",
"contentType": "application/pdf"
}
],
"tag": "invoice",
"metadata": {
"invoiceId": "2026-0912"
}
}Responses
200Accepted for delivery.
messageIdstring (uuid)requiredThe id the message log, the events and the webhooks refer to.
status"accepted"requiredsubmittedAtstring (date-time)requiredWhen the message was accepted.
testbooleanrequiredTrue when the server is a test server: the message goes through everything but delivery, and nobody receives it.
Idempotent-Replayedheader"true"true when this is the first answer again, to a request sent before under the same Idempotency-Key; absent otherwise.
Example
{
"messageId": "5f432ffd-c005-499b-aa3a-5b8a088ea20e",
"status": "accepted",
"submittedAt": "2026-09-15T12:00:00.000Z",
"test": false
}Errors
Every error answers error, the code, and message, a sentence for a person. A code that adds fields is shown in full below the table.
| Code | Status | Meaning |
|---|---|---|
invalid_ | 400 | The body, the query or a header does not match what the route takes. |
unauthorized | 401 | The key is missing, malformed or revoked. |
payment_ | 402 | The account has no active subscription. |
wrong_ | 403 | The key is of the other kind: a server key (sk_) where an account key (ak_) is needed, or the reverse. The message names the kind the operation takes. |
account_ | 403 | The account is paused by Sendora. |
account_ | 403 | The account is closed, or not yet approved for a live server's sends or an inbound stream. |
request_ | 413 | The body exceeds 10 MB. |
from_ | 422 | The From domain is not a verified sending domain of this server. |
stream_ | 422 | The streamId names no stream of this server. |
stream_ | 422 | The stream is archived and takes no new messages. |
stream_ | 422 | Sendora has paused the stream after complaints; it takes no messages until support has resumed it. |
stream_ | 422 | An inbound stream receives mail; it takes no messages and has no suppression list. Name a transactional or broadcast stream. |
unsubscribe_ | 422 | A message on a broadcast stream must carry {{ unsubscribe_url }} in every part it has. |
list_ | 422 | On a broadcast stream Sendora writes the List-Unsubscribe pair itself, so a message may not carry one. |
recipient_ | 422 | One or more recipients are on the suppression list of the stream. |
test_ | 422 | Addresses at simulator.sendora.se act out an outcome on a test server; a live server never sends to them. |
idempotency_ | 422 | The Idempotency-Key was already used for a different request. |
rate_ | 429 | More was sent within a minute than the limit allows. |
monthly_ | 429 | The monthly cap of the account, the server or the test servers is used up. |
sending_ | 503 | Sending is disabled for everyone for the moment; retry after the seconds in Retry-After. |
Retry-Afterheader on 429integerWith rate_limited, the seconds until a send is accepted; absent with monthly_cap_reached.
Retry-Afterheader on 503integerSeconds to wait before trying again.
invalid_request
issuesarray of objectOne entry per invalid field; absent when a header is wrong.
pathstringrequiredThe field, dotted, such as to.0.email; empty when the whole body is wrong.
messagestringrequired
Example
{
"error": "invalid_request",
"message": "The request is invalid: to.0: Invalid email address",
"issues": [
{
"path": "to.0",
"message": "Invalid email address"
}
]
}recipient_suppressed
streamIdstring (uuid)requiredThe stream whose suppression list refused the send.
suppressedarray of objectrequiredEach refused address, lower-cased, with the reason it is on the list.
addressstringrequiredreason"hard_bounce" | "spam_ requiredcomplaint" | "manual" | "unsubscribe"
Example
{
"error": "recipient_suppressed",
"message": "One or more recipients are on the suppression list of this stream; suppressed says which and why.",
"streamId": "3d1a2b4c-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"suppressed": [
{
"address": "anna@example.com",
"reason": "hard_bounce"
}
]
}test_address_on_live_server
addressesarray of stringrequiredThe recipients at simulator.sendora.se, each once.
Example
{
"error": "test_address_on_live_server",
"message": "Addresses at simulator.sendora.se act out an outcome on a test server and are never sent to from a live one. Send them from a test server.",
"addresses": [
"hardbounce@simulator.sendora.se"
]
}rate_limited
scope"account" | "server" | "test"requiredWhose limit it is:
accountfor the account's,serverfor the server's own,testfor the cap the account's test servers share.limitintegerrequiredEmails per minute allowed for that scope.
retryAfterintegerrequiredSeconds until the next send is accepted; also sent as the Retry-After header.
Example
{
"error": "rate_limited",
"message": "This server may send 60 emails per minute. Try again in 12 seconds.",
"scope": "server",
"limit": 60,
"retryAfter": 12
}monthly_cap_reached
scope"account" | "server" | "test"requiredWhose limit it is:
accountfor the account's,serverfor the server's own,testfor the cap the account's test servers share.capintegerrequiredEmails the scope may send in a calendar month, in UTC.
usedintegerrequiredEmails sent this month, the refused send not counted.
resetsAtstring (date-time)requiredWhen the month counter resets.
Example
{
"error": "monthly_cap_reached",
"message": "This account has used 50000 of 50000 emails this month. The cap resets at 2026-11-01T00:00:00.000Z.",
"scope": "account",
"cap": 50000,
"used": 50000,
"resetsAt": "2026-11-01T00:00:00.000Z"
}