Hoppa till innehållet

Get started

Quickstart

Three steps take you from an approved account to a delivered message.

Before you start

  • Your account is approved, and payment is active. Activate payment under Account › Billing once you are approved. Until then a live server’s sends fail, with the codes on Errors.
  • The domain you send from is verified, as Domains explains. The Domains page shows its two DNS records: a CNAME for the return path and a TXT record for the DKIM key.
  • To try this without sending real mail, use a test server and its key. A test server works before your account is approved and before payment.

1. Get a server key

Sign in to the dashboard and open your server under Servers. Signup made a server named default, without a key. Create a key on its API keys tab. The key is shown once, so put it in your secret store. Keys explains the two kinds of key.

Install the SDK. It has no dependencies, is ESM, and runs on Node 20.19 or newer, Bun, Deno and Cloudflare Workers:

npm install @sendora/sdk

Keep the key in the environment on the server, as SENDORA_API_TOKEN below. Never ship it to a browser. The client throws when you create it without a key.

Install the SDK. It runs on Python 3.11 or newer and has one dependency, httpx2:

pip install sendora

Pin the major version you test with, such as sendora~=1.0 in your requirements. Only a new major version may break your code.

Keep the key in the environment on the server, as SENDORA_API_TOKEN below. Sendora() reads it from there when you give it no key, and raises ValueError when there is none.

Send the key as a bearer token on every request:

Authorization: Bearer sk_…

2. Send a message

POST /v1/email
curl -X POST https://api.sendora.se/v1/email \
  -H "Authorization: Bearer $SENDORA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "from": {
    "email": "no-reply@example.se",
    "name": "Example AB"
  },
  "to": [
    "anna@example.com"
  ],
  "subject": "Your account is ready",
  "text": "Hi Anna, your account at Example is ready. Sign in at https://example.se/login.",
  "tag": "welcome"
}'
import { Sendora } from '@sendora/sdk';

const sendora = new Sendora({
  token: process.env.SENDORA_API_TOKEN,
});

const { messageId } = await sendora.email.send({
  from: {
    email: 'no-reply@example.se',
    name: 'Example AB',
  },
  to: ['anna@example.com'],
  subject: 'Your account is ready',
  text: 'Hi Anna, your account at Example is ready. Sign in at https://example.se/login.',
  tag: 'welcome',
});

console.log(messageId);
import os

from sendora import Sendora

sendora = Sendora(os.environ["SENDORA_API_TOKEN"])

accepted = sendora.email.send(
    from_={
        "email": "no-reply@example.se",
        "name": "Example AB",
    },
    to=["anna@example.com"],
    subject="Your account is ready",
    text=(
        "Hi Anna, your account at Example is ready. "
        "Sign in at https://example.se/login."
    ),
    tag="welcome",
)

print(accepted.message_id)

Response

{
  "messageId": "5f432ffd-c005-499b-aa3a-5b8a088ea20e",
  "status": "accepted",
  "submittedAt": "2026-09-15T12:00:00.000Z",
  "test": false
}

The from address must be on a verified domain. to, cc and bcc take plain addresses or objects with a display name. A message needs a subject and at least one of text and html.

The answer comes at once. accepted means Sendora has stored the message and will hand it to its mail server. The log, the events and the webhooks all refer to the message by its messageId. Delivery, deferral, bounce and complaint arrive later, as events.

If your code may retry a send, give it an idempotency key. A retry with the same key returns the first answer instead of sending twice. Sending explains keys and batches.

3. Read the message

The message log keeps every message with its recipients and its timeline, for the time on Limits. Read one 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
    }
  ]
}

The message’s status turns injected once Sendora’s mail server has it. Each recipient’s status moves from queued to delivered, deferred or bounced as the receiving server answers. The events list is the timeline: Reception when Sendora’s mail server took the message, Delivery when the receiver accepted it, and Bounce when it refused it.

The server’s Messages tab in the dashboard shows the same log, searchable by recipient, tag, status and time.

What next

  • Register a webhook to get delivered, bounced, deferred and spam_complaint events as they happen.
  • Send from a system that only speaks SMTP, as SMTP submission shows.
  • Look up any route, with its fields and error codes, in the API reference.