Hoppa till innehållet

After sending

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 happenedStatusTimeline eventWebhook event
Sendora’s mail server took the messagequeuedReceptionnone
The receiver accepted itdeliveredDeliverydelivered
The receiver asked Sendora to try laterdeferredTransientFailuredeferred
The receiver refused itbouncedBouncebounced
A bounce report arrived later by mailbouncedOOBbounced
The receiver was still deferring after three daysexpiredExpirationbounced, with expired: true
The recipient reported it as spamunchangedFeedbackspam_complaint
The recipient unsubscribed from a broadcast streamunchangednoneunsubscribed

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:

GET /v1/messages/{messageId}
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:

FilterMatches
recipientMessages to this address, in to, cc or bcc
streamIdMessages on this stream
broadcastIdMessages of this broadcast
tagMessages with this tag
statusMessages with at least one recipient in this status
fromMessages submitted at or after this time
toMessages 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.

POST /v1/messages/search
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.searchAll takes the same filters and walks every page for you, for for await. Its from and to also take a Date.

messages.search_all 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.