Hoppa till innehållet

Tools

SMTP submission

Use SMTP when a system cannot call an HTTP API, such as an ERP or a printer. A message sent over SMTP goes through the same checks as an API send. It appears in the same log, gets the same events and counts against the same limits.

Connect

SettingValue
Hostsmtp.sendora.se
Port465 with TLS from the start, or 587 with STARTTLS
User name and passwordA server key, sk_… or a test server’s sk_test_…, as both
AuthenticationPLAIN or LOGIN, once TLS is on

The From address must be on one of the server’s verified domains. The message limits apply as they do over the API, with To, Cc and Bcc counted together.

import nodemailer from 'nodemailer';

/** Sends one message over SMTP with a server's API token as both user name and password. */
export async function sendOverSmtp(
  token: string,
): Promise<string> {
  const transport = nodemailer.createTransport({
    host: 'smtp.sendora.se',
    port: 587,
    secure: false,
    requireTLS: true,
    auth: { user: token, pass: token },
  });
  const info = await transport.sendMail({
    from: '"Example AB" <no-reply@example.se>',
    to: 'anna@example.com',
    subject: 'Your account is ready',
    text: 'Hi Anna, your account at Example is ready.',
    headers: {
      'X-Sendora-Tag': 'welcome',
      'X-Sendora-Metadata-Customer': '4711',
    },
  });
  return info.response; // "250 2.0.0 OK: queued as <messageId>"
}
import smtplib
import ssl
from email.message import EmailMessage


# Sends one message over SMTP with a server's API
# token as both user name and password.
def send_over_smtp(token: str) -> None:
    message = EmailMessage()
    message["From"] = "Example AB <no-reply@example.se>"
    message["To"] = "anna@example.com"
    message["Subject"] = "Your account is ready"
    message["X-Sendora-Tag"] = "welcome"
    message["X-Sendora-Metadata-Customer"] = "4711"
    message.set_content(
        "Hi Anna, your account at Example is ready."
    )

    # Without a context of its own, starttls() would
    # accept any certificate, and without a timeout
    # smtplib waits forever on a stalled connection.
    context = ssl.create_default_context()
    with smtplib.SMTP(
        "smtp.sendora.se", 587, timeout=30
    ) as smtp:
        smtp.starttls(context=context)
        smtp.login(token, token)
        smtp.send_message(message)

Sendora delivers only to the envelope recipients, the addresses your client gives in RCPT TO. The To and Cc headers decide how each of them is shown, and an envelope recipient that neither header names is sent as Bcc. An address that only a header names gets nothing.

Send from Django

Django sends over SMTP out of the box and checks the server’s certificate when it starts TLS. In settings.py, use the server key from the environment as both user name and password. DEFAULT_FROM_EMAIL must be on a verified domain, and so must SERVER_EMAIL, the address Django sends its error mail from.

import os

# In settings.py, for Django's SMTP backend, its
# default: the server key as user name and password.
EMAIL_HOST = "smtp.sendora.se"
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = os.environ["SENDORA_API_TOKEN"]
EMAIL_HOST_PASSWORD = os.environ["SENDORA_API_TOKEN"]
EMAIL_TIMEOUT = 30
DEFAULT_FROM_EMAIL = "Example AB <no-reply@example.se>"
SERVER_EMAIL = DEFAULT_FROM_EMAIL

Django 6.1 replaces the EMAIL_* settings with MAILERS and refuses to start with both. On 6.1, put the same values in MAILERS["default"]["OPTIONS"] as host, port, use_tls, username, password and timeout. DEFAULT_FROM_EMAIL and SERVER_EMAIL stay as they are.

Set the stream, tag and metadata

HeaderSets
X-Sendora-StreamThe stream the message goes on, by its id
X-Sendora-TagThe message’s tag
X-Sendora-Metadata-<key>One metadata entry: X-Sendora-Metadata-Customer: 4711 sets { "customer": "4711" }

Without X-Sendora-Stream, the message goes on the server’s default transactional stream.

Attachments are ordinary MIME parts. Sendora also keeps Reply-To, List-Unsubscribe, List-Unsubscribe-Post and your own X- headers. It drops other headers, such as Message-ID, Received or DKIM-Signature, and writes its own. The API refuses these headers instead of dropping them.

SMTP replies

Sendora accepts or refuses a message before the session ends. A 250 means the message is stored and already in the log, and Sendora never refuses it after that. Delivery outcomes, such as a bounce, arrive later as events. A 5xx reply means a retry will not help. A 4xx reply means a retry may succeed.

ReplyWhen
250 2.0.0 OK: queued as <messageId>Accepted. The id is the one the log and the webhooks use.
530 5.7.0The client sent a message without authenticating.
535 5.7.8The key is wrong, revoked or an account key, or the user name and password differ.
550 5.7.1The From domain is not verified, or the account is paused. On a live server, the account is not approved or without active payment. It also covers a stream in X-Sendora-Stream that is unknown, archived, paused or inbound.
550 5.1.1A recipient is suppressed, or a live server was given an address at simulator.sendora.se. The text lists the addresses.
550 5.6.0The API would refuse the message as invalid, or it breaks the rules of a broadcast stream. The text gives the reasons.
552 5.3.4The message is over the size limit.
452 4.5.3The message has more recipients than a message may have. Split them over several messages.
451 4.7.0The per-minute limit or the monthly cap is reached. The text says when to try again.
451 4.3.2Sending is disabled for everyone for the moment.
451 4.3.0A temporary failure on Sendora’s side.