Hoppa till innehållet

Send a broadcast

POST/v1/broadcasts

TypeScript sendora.broadcasts.send(broadcast)

Python sendora.broadcasts.send(**broadcast)

Key sk_… a server key

Stores the content once and one message per entry of messages, up to 50,000, on a broadcast stream, in one transaction. Each message is a message in the log with its own events and webhooks, sent with the recipient’s own unsubscribe link where {{ unsubscribe_url }} stands. Each message may carry substitutions, up to 20 strings that replace {{ key }} in the subject, the text and the HTML, escaped in the HTML; a key the content names must be given by every message. Addresses on the stream’s suppression list are dropped and counted; a list with nothing left, a missing placeholder, a missing substitution, a transactional or archived stream and a count over the monthly cap are refused whole. The Idempotency-Key is required: a retry under the same key answers the same broadcast.

Parameters

  • idempotency-keyin headerstringrequired

    Any text of 1 to 255 printable characters that identifies this broadcast; the same key with a different body is refused.

Request body

  • streamIdstring (uuid)required

    The broadcast stream of this server the messages go on.

  • fromone of 2required

    The sender. Its domain must be a verified sending domain of the server.

    1. string (email)
      • emailstring (email)required
      • namestring (at least 1, at most 200 characters) or null

        A display name of up to 200 characters.

  • subjectstring (at least 1, at most 998 characters)required

    The subject line.

  • textstring (at least 1 character)

    The plain-text part, with {{ unsubscribe_url }} where the link goes. At least one of text and html is required.

  • htmlstring (at least 1 character)

    The HTML part, with {{ unsubscribe_url }} where the link goes.

  • headersmap of string to stringdefault {}

    Custom headers on every message, at most 20. The List-Unsubscribe pair is Sendora’s too. 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 20

    At most 20, sent with every message. A message with its attachments encoded may be at most 10 MB.

    • namestringrequired
    • contentstring (base64)required
    • contentTypestring
    • contentIdstring or null
  • tagstring or null

    A 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, on the broadcast and on every message.

  • messagesarray of object, at least 1, at most 50000required

    One entry per message, at most 50000; a larger list is several broadcasts under one tag.

    • toarray of one of 2, at least 1required

      Recipients of this message; at most 50 across to, cc and bcc.

      1. string (email)
        • emailstring (email)required
        • namestring (at least 1, at most 200 characters) or null

          A display name of up to 200 characters.

    • ccarray of one of 2

      Copied recipients, shown in the message.

      1. string (email)
        • emailstring (email)required
        • namestring (at least 1, at most 200 characters) or null

          A display name of up to 200 characters.

    • bccarray of one of 2

      Blind-copied recipients.

      1. string (email)
        • emailstring (email)required
        • namestring (at least 1, at most 200 characters) or null

          A display name of up to 200 characters.

    • metadatamap of string to stringdefault {}

      Up to 20 key-value pairs for this message, merged over the broadcast's.

    • headersmap of string to stringdefault {}

      Custom headers for this message, merged over the broadcast's.

    • substitutionsmap of string to stringdefault {}

      Up to 20 strings that replace {{ key }} in the subject, the text and the HTML of this message; a key the content names must be given.

{
  "streamId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "from": {
    "email": "news@acme.se",
    "name": "Acme"
  },
  "subject": "News from Acme in October",
  "text": "Hi!\n\nHere is the news for October.\n\nRather not get these? {{ unsubscribe_url }}",
  "html": "<p>Hi!</p><p>Here is the news for October.</p><p><a href=\"{{ unsubscribe_url }}\">Unsubscribe</a></p>",
  "tag": "newsletter-2026-10",
  "messages": [
    {
      "to": [
        "anna@example.com"
      ],
      "metadata": {
        "customer": "1042"
      }
    },
    {
      "to": [
        {
          "email": "bo@example.com",
          "name": "Bo Berg"
        }
      ]
    }
  ]
}

Responses

200The broadcast was stored; Sendora sends its messages from here.

  • broadcastIdstring (uuid)required

    The id the messages, the progress and the cancel refer to.

  • status"accepted"required
  • totalintegerrequired

    Messages stored; each is a message in the log.

  • suppressedintegerrequired

    Addresses dropped for standing on the stream’s suppression list.

  • submittedAtstring (date-time)required
  • testbooleanrequired

    True when the server is a test server: the messages go through everything but delivery, and nobody receives them.

  • Idempotent-Replayedheader"true"

    true when this is the first answer again, to a request sent before under the same Idempotency-Key; absent otherwise.

Example

{
  "broadcastId": "2f1c8a4e-3b6d-4f0a-9c21-7d5e6a8b9c01",
  "status": "accepted",
  "total": 2,
  "suppressed": 0,
  "submittedAt": "2026-10-01T08: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.

CodeStatusMeaning
invalid_request400The body, the query or a header does not match what the route takes.
idempotency_key_required400Batch sends need an Idempotency-Key header.
unauthorized401The key is missing, malformed or revoked.
payment_required402The account has no active subscription.
wrong_token_kind403The 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_paused403The account is paused by Sendora.
account_not_active403The account is closed, or not yet approved for a live server's sends or an inbound stream.
request_too_large413The body exceeds 10 MB.
from_domain_not_verified422The From domain is not a verified sending domain of this server.
stream_not_found422The streamId names no stream of this server.
stream_archived422The stream is archived and takes no new messages.
stream_paused422Sendora has paused the stream after complaints; it takes no messages until support has resumed it.
stream_not_broadcast422A broadcast goes on a broadcast stream; the streamId names a transactional one.
unsubscribe_placeholder_missing422A message on a broadcast stream must carry {{ unsubscribe_url }} in every part it has.
list_unsubscribe_reserved422On a broadcast stream Sendora writes the List-Unsubscribe pair itself, so a message may not carry one.
substitution_missing422The subject, text or HTML names a {{ key }} that a message does not give.
recipient_suppressed422One or more recipients are on the suppression list of the stream.
test_address_on_live_server422Addresses at simulator.sendora.se act out an outcome on a test server; a live server never sends to them.
idempotency_key_mismatch422The Idempotency-Key was already used for a different request.
monthly_cap_reached429The monthly cap of the account, the server or the test servers is used up.
sending_disabled503Sending is disabled for everyone for the moment; retry after the seconds in Retry-After.
  • Retry-Afterheader on 503integer

    Seconds to wait before trying again.

invalid_request

  • issuesarray of object

    One entry per invalid field; absent when a header is wrong.

    • pathstringrequired

      The 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"
    }
  ]
}

substitution_missing

  • indexintegerrequired

    The position of the message in messages.

  • keysarray of stringrequired

    The keys the content names that the message lacks.

Example

{
  "error": "substitution_missing",
  "message": "The content names {{ firstName }}, which message 1 does not give.",
  "index": 1,
  "keys": [
    "firstName"
  ]
}

recipient_suppressed

  • streamIdstring (uuid)required

    The stream whose suppression list refused the send.

  • suppressedarray of objectrequired

    Each refused address, lower-cased, with the reason it is on the list.

    • addressstringrequired
    • reason"hard_bounce" | "spam_complaint" | "manual" | "unsubscribe"required

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 stringrequired

    The 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"
  ]
}

monthly_cap_reached

  • scope"account" | "server" | "test"required

    Whose limit it is: account for the account's, server for the server's own, test for the cap the account's test servers share.

  • capintegerrequired

    Emails the scope may send in a calendar month, in UTC.

  • usedintegerrequired

    Emails sent this month, the refused send not counted.

  • resetsAtstring (date-time)required

    When 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"
}