After sending
More ways to use this page
Messages and events
Every message you send is in the log, with its recipients, their statuses and a timeline of what Sendora’s mail server and the receivers said about it. Sendora keeps a message’s text and HTML only to deliver it, and never returns them, so keep what you send. The server’s Messages tab under Servers in the dashboard shows the same log.
Statuses
A message has a status of its own, which says whether Sendora’s mail server has it: accepted once stored, injected once the mail server has it, and failed if that never happened. The outcome for each recipient is in the recipient’s status.
Each recipient has a status too, which starts as queued. This table maps what happened to the recipient’s status, the timeline’s event and the webhook event:
| What happened | Status | Timeline event | Webhook event |
|---|---|---|---|
| Sendora’s mail server took the message | queued | Reception | none |
| The receiver accepted it | delivered | Delivery | delivered |
| The receiver asked Sendora to try later | deferred | TransientFailure | deferred |
| The receiver refused it | bounced | Bounce | bounced |
| A bounce report arrived later by mail | bounced | OOB | bounced |
| The receiver was still deferring after three days | expired | Expiration | bounced, with expired: true |
| The recipient reported it as spam | unchanged | Feedback | spam_ |
| The recipient unsubscribed from a broadcast stream | unchanged | none | unsubscribed |
A status only moves forward, so a late deferral never undoes a delivery. delivered, bounced and expired are final. The one exception is a bounce report that arrives later, which turns delivered into bounced.
A timeline event has the SMTP code and the receiver’s text in details, where there is one. A Bounce also has a classification, such as InvalidRecipient, where Sendora can tell. Other event types are rare, and the recipient’s status reflects them.
Read a message with its timeline by its id:
curl https://api.sendora.se/v1/messages/{messageId} \
-H "Authorization: Bearer $SENDORA_API_TOKEN"import { Sendora } from '@sendora/sdk';
const sendora = new Sendora({
token: process.env.SENDORA_API_TOKEN,
});
const message = await sendora.messages.get(
'5f432ffd-c005-499b-aa3a-5b8a088ea20e',
);
for (const recipient of message.recipients) {
console.log(recipient.address, recipient.status);
}
for (const event of message.events) {
console.log(
event.at,
event.type,
event.code,
event.details,
);
}
import os
from sendora import Sendora
sendora = Sendora(os.environ["SENDORA_API_TOKEN"])
message = sendora.messages.get(
"5f432ffd-c005-499b-aa3a-5b8a088ea20e"
)
for recipient in message.recipients:
print(recipient.address, recipient.status)
for event in message.events:
print(event.at, event.type, event.code, event.details)
Response (38 lines)
{
"messageId": "5f432ffd-c005-499b-aa3a-5b8a088ea20e",
"streamId": "3d1a2b4c-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"broadcastId": null,
"status": "injected",
"from": "no-reply@example.se",
"subject": "Your account is ready",
"tag": "welcome",
"metadata": {},
"submittedAt": "2026-09-15T12:00:00.000Z",
"test": false,
"recipients": [
{
"address": "anna@example.com",
"kind": "to",
"status": "delivered"
}
],
"attachments": [],
"events": [
{
"recipient": "anna@example.com",
"type": "Reception",
"at": "2026-09-15T12:00:00.000Z",
"code": 250,
"details": null,
"classification": null
},
{
"recipient": "anna@example.com",
"type": "Delivery",
"at": "2026-09-15T12:00:02.000Z",
"code": 250,
"details": "2.0.0 OK",
"classification": null
}
]
}Search the log
Every filter is optional:
| Filter | Matches |
|---|---|
recipient | Messages to this address, in to, cc or bcc |
streamId | Messages on this stream |
broadcastId | Messages of this broadcast |
tag | Messages with this tag |
status | Messages with at least one recipient in this status |
from | Messages submitted at or after this time |
to | Messages submitted before this time |
from and to are times, not addresses. A recipient address travels in the request body, never in a URL. The results are newest first, in the page sizes on Limits. Pass next as after for the next page.
curl -X POST https://api.sendora.se/v1/messages/search \
-H "Authorization: Bearer $SENDORA_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"tag": "welcome",
"from": "2026-09-01T00:00:00Z",
"limit": 20
}'import { Sendora } from '@sendora/sdk';
const sendora = new Sendora({
token: process.env.SENDORA_API_TOKEN,
});
const page = await sendora.messages.search({
tag: 'welcome',
from: '2026-09-01T00:00:00Z',
limit: 20,
});
for (const message of page.messages) {
console.log(
message.submittedAt,
message.subject,
message.recipients[0]?.status,
);
}
console.log(
page.next === null
? 'last page'
: `next page after ${page.next}`,
);
import os
from sendora import Sendora
sendora = Sendora(os.environ["SENDORA_API_TOKEN"])
page = sendora.messages.search(
tag="welcome", from_="2026-09-01T00:00:00Z", limit=20
)
for message in page.messages:
print(
message.submitted_at,
message.subject,
message.recipients[0].status,
)
if page.next is None:
print("last page")
else:
print(f"next page after {page.next}")
Response (24 lines)
{
"messages": [
{
"messageId": "5f432ffd-c005-499b-aa3a-5b8a088ea20e",
"streamId": "3d1a2b4c-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"broadcastId": null,
"status": "injected",
"from": "no-reply@example.se",
"subject": "Your account is ready",
"tag": "welcome",
"metadata": {},
"submittedAt": "2026-09-15T12:00:00.000Z",
"test": false,
"recipients": [
{
"address": "anna@example.com",
"kind": "to",
"status": "delivered"
}
]
}
],
"next": null
}messages takes the same filters and walks every page for you, for for await. Its from and to also take a Date.
messages takes the same filters and walks every page for you, as an iterator. Its from_ and to also take an aware datetime.
How long a message is kept
Sendora deletes the content of a message’s attachments as soon as its mail server has the message, or once the message has failed. Their names, types and sizes stay in the log. The text and HTML go next, and the message, its recipients and its events last. Limits gives each period, and a test server keeps less.
When a recipient asks you to erase their data, use an erasure request.