# Website e-mail sending

Send a website's e-mail through your own provider (Resend, SendGrid, Brevo, Mailgun, Amazon SES, Postmark or any SMTP server) instead of our mail servers, and test it.

## Providers you can send through

`GET /mail-sending/providers` · scope `sites:read`

The providers a site can send through and what each needs: a key only (Resend, SendGrid, Postmark), a login and key (Brevo, Mailgun, Amazon SES, which also need a region), or a server, port, user name and password (provider "smtp").

### Example

```bash
curl -s "https://app.pbn.ltd/api/v1/mail-sending/providers" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/mail-sending/providers", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/mail-sending/providers", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": [
    {
      "key": "resend",
      "name": "Resend",
      "host": "smtp.resend.com",
      "ports": [
        465,
        587
      ],
      "default_port": 465,
      "asks_user": false,
      "asks_host": false,
      "user_label": "",
      "secret_label": "API key",
      "regions": null,
      "docs": "https://resend.com/docs/send-with-smtp"
    }
  ]
}
```

Errors: `rate_limited`, `scope_missing`, `unauthorized`

## Where a site's e-mail goes out

`GET /sites/{site_id}/mail-sending` · scope `sites:read`

"route": "ours" = our mail servers (the default); "own" = the customer's own provider, with its state ("pending" while being set up on the server, "active", "error" + state_note). The key is never returned, only key_hint.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `site_id` | path | integer | yes | The site id (see GET /sites). |

### Example

```bash
curl -s "https://app.pbn.ltd/api/v1/sites/123/mail-sending" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/sites/123/mail-sending", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/mail-sending", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "route": "own",
    "provider": "resend",
    "provider_name": "Resend",
    "region": null,
    "host": "smtp.resend.com",
    "port": 465,
    "username": null,
    "key_hint": "...9f2a",
    "from_address": "noreply@example.com",
    "force_from": true,
    "enabled": true,
    "locked_by_staff": false,
    "state": "active",
    "state_note": "",
    "confirmed_at": "2026-09-26T16:00:00Z",
    "last_test": {
      "at": "2026-09-26T16:01:00Z",
      "ok": true,
      "to": "you@example.com",
      "message": "Sent. Resend accepted the test e-mail (250 OK)."
    },
    "last_send": null,
    "updated_at": "2026-09-26T16:00:00Z"
  }
}
```

Errors: `not_found`, `rate_limited`, `scope_missing`, `unauthorized`

## Send through my own provider

`PUT /sites/{site_id}/mail-sending` · scope `sites:write`

Stores the settings (the key encrypted) and switches the site's e-mail to that provider within a minute. Check "state", then POST .../mail-sending/test.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `site_id` | path | integer | yes | The site id (see GET /sites). |
| `provider` | body | string (one of: resend, sendgrid, brevo, mailgun, ses, postmark, smtp) | yes | See GET /mail-sending/providers. |
| `secret` | body | string | no | The API key / SMTP key / SMTP password. Required the first time and when the provider or server changes; leave out to keep the stored one. |
| `username` | body | string | no | The SMTP login (Brevo, Mailgun, Amazon SES, smtp). |
| `region` | body | string | no | Amazon SES: e.g. eu-west-1. Mailgun: us or eu. |
| `host` | body | string | no | provider "smtp" only: the SMTP server name. |
| `port` | body | integer (one of: 465, 587) | no | 465 (TLS) or 587 (STARTTLS). |
| `from_address` | body | string | no | The address the website sends as (default noreply@<site domain>). Must be verified at the provider. |
| `force_from` | body | boolean | no | Always send as from_address (recommended: providers refuse unverified senders). Default: `True`. |

### Example

```bash
curl -s -X PUT "https://app.pbn.ltd/api/v1/sites/123/mail-sending" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.put("https://app.pbn.ltd/api/v1/sites/123/mail-sending", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/mail-sending", {
  method: "PUT",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "route": "own",
    "provider": "resend",
    "provider_name": "Resend",
    "region": null,
    "host": "smtp.resend.com",
    "port": 465,
    "username": null,
    "key_hint": "...9f2a",
    "from_address": "noreply@example.com",
    "force_from": true,
    "enabled": true,
    "locked_by_staff": false,
    "state": "active",
    "state_note": "",
    "confirmed_at": "2026-09-26T16:00:00Z",
    "last_test": {
      "at": "2026-09-26T16:01:00Z",
      "ok": true,
      "to": "you@example.com",
      "message": "Sent. Resend accepted the test e-mail (250 OK)."
    },
    "last_send": null,
    "updated_at": "2026-09-26T16:00:00Z"
  }
}
```

Errors: `not_found`, `rate_limited`, `scope_missing`, `unauthorized`, `unavailable`, `validation_failed`

## Send through our mail servers again

`DELETE /sites/{site_id}/mail-sending` · scope `sites:write` · **destructive**

Switches the site back to our mail servers within a minute. The settings are kept unless forget=true.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `site_id` | path | integer | yes | The site id (see GET /sites). |
| `forget` | query | boolean | no | Also delete the stored key and settings. Default: `False`. |

### Example

```bash
curl -s -X DELETE "https://app.pbn.ltd/api/v1/sites/123/mail-sending" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.delete("https://app.pbn.ltd/api/v1/sites/123/mail-sending", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/mail-sending", {
  method: "DELETE",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "route": "ours",
    "provider": null
  }
}
```

Errors: `not_found`, `rate_limited`, `scope_missing`, `unauthorized`

## Send a test e-mail

`POST /sites/{site_id}/mail-sending/test` · scope `sites:write`

Sends one real e-mail the way the website does (from inside the site, through the provider) and returns the provider's answer in plain words. At most 5 tests per site per 10 minutes.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `site_id` | path | integer | yes | The site id (see GET /sites). |
| `to` | body | string | yes | Where to send the test e-mail. |

### Example

```bash
curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/mail-sending/test" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/sites/123/mail-sending/test", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/mail-sending/test", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "ok": true,
    "message": "Sent. Resend accepted the test e-mail."
  }
}
```

Errors: `not_found`, `rate_limited`, `scope_missing`, `unauthorized`, `validation_failed`
