# SMTP submission

Send from a system that only speaks SMTP, with the same key and rules.

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

| Setting                | Value                                                                              |
| ---------------------- | ---------------------------------------------------------------------------------- |
| Host                   | `smtp.sendora.se`                                                                  |
| Port                   | 465 with TLS from the start, or 587 with STARTTLS                                  |
| User name and password | A server key, `sk_…` or a [test server](https://sendora.se/docs/test-servers)'s `sk_test_…`, as both |
| Authentication         | `PLAIN` or `LOGIN`, once TLS is on                                                 |

The `From` address must be on one of the server's verified domains. The [message limits](https://sendora.se/docs/limits#messages) apply as they do over the API, with `To`, `Cc` and `Bcc` counted together.

TypeScript:

```ts
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>"
}
```

Python:

```python
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.

```python
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

| Header                     | Sets                                                                                  |
| -------------------------- | ------------------------------------------------------------------------------------- |
| `X-Sendora-Stream`         | The [stream](https://sendora.se/docs/streams) the message goes on, by its id                            |
| `X-Sendora-Tag`            | The 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.

| Reply                                 | When                                                                                                                                                                                                                              |
| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `250 2.0.0 OK: queued as <messageId>` | Accepted. The id is the one the log and the webhooks use.                                                                                                                                                                         |
| `530 5.7.0`                           | The client sent a message without authenticating.                                                                                                                                                                                 |
| `535 5.7.8`                           | The key is wrong, revoked or an [account key](https://sendora.se/docs/keys), or the user name and password differ.                                                                                                                                  |
| `550 5.7.1`                           | The `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.1`                           | A recipient is suppressed, or a live server was given an address at `simulator.sendora.se`. The text lists the addresses.                                                                                                         |
| `550 5.6.0`                           | The API would refuse the message as invalid, or it breaks the [rules of a broadcast stream](https://sendora.se/docs/broadcasts#rules-of-a-broadcast-stream). The text gives the reasons.                                                            |
| `552 5.3.4`                           | The message is over the [size limit](https://sendora.se/docs/limits#messages).                                                                                                                                                                      |
| `452 4.5.3`                           | The message has more recipients than [a message may have](https://sendora.se/docs/limits#messages). Split them over several messages.                                                                                                               |
| `451 4.7.0`                           | The per-minute limit or the monthly cap is reached. The text says when to try again.                                                                                                                                              |
| `451 4.3.2`                           | Sending is disabled for everyone for the moment.                                                                                                                                                                                  |
| `451 4.3.0`                           | A temporary failure on Sendora's side.                                                                                                                                                                                            |
