
# Configure Email Sending

::: tip Recommended
Use Cloudflare `send_email` binding as the default send channel. Bind `SEND_MAIL` and finish Email Routing onboarding, then the Worker can send to any external address directly.

Workers Paid includes 3,000 messages/month, then $0.35 per 1,000 messages.
:::

## Send Channel Priority

Each `/api/send_mail` request matches channels in order; **the first hit sends**:

| Order | Condition | Channel | Deducts balance |
|-------|-----------|---------|----------------|
| 1 | `SEND_MAIL` bound **AND** recipient in `verifiedAddressList` | Cloudflare binding (compat mode) | No |
| 2 | `RESEND_TOKEN` or `RESEND_TOKEN_<DOMAIN>` set | Resend API | Yes |
| 3 | `SMTP_CONFIG` has entry for current domain | worker-mailer SMTP | Yes |
| 4 | `SEND_MAIL` bound (none of the above) | **Cloudflare binding (recommended primary)** | Yes |
| — | None of the above | Throws | — |

> [!NOTE]
> Binding send failures return an error directly.

## Using the Cloudflare `send_email` Binding (Recommended)

Only available when deploying via CLI. Add to `wrangler.toml`:

```toml
# Send emails via the Cloudflare send_email binding
send_email = [
   { name = "SEND_MAIL" },
]
```

> [!warning] Important
> The binding name must be `SEND_MAIL` — different from Cloudflare's official `SEND_EMAIL` example.

After the following steps, you can send to any external address directly:

1. Enable Email Routing on the domain in the Cloudflare Dashboard and complete onboarding
2. Add the `send_email` binding shown above to `wrangler.toml`
3. Deploy the Worker

No additional env var is required.

## Send Emails Using Resend

Register at `https://resend.com/domains` and add DNS records according to the instructions.

Create an `api key` on the `API KEYS` page.

Then execute the following command to add `RESEND_TOKEN` to secrets:

> [!NOTE]
> If you find this troublesome, you can also put it directly in plain text under `[vars]` in `wrangler.toml`, but this is not recommended

If you deployed through the UI, you can add it under `Variables and Secrets` in the Cloudflare UI interface.

```bash
# Switch to worker directory
cd worker
wrangler secret put RESEND_TOKEN
```

If you have multiple domains with different `api keys`, you can add multiple secrets in `wrangler.toml`, named `RESEND_TOKEN_` + `<UPPERCASE DOMAIN WITH . REPLACED BY _>`, for example:

```bash
wrangler secret put RESEND_TOKEN_XXX_COM
wrangler secret put RESEND_TOKEN_DREAMHUNTER2333_XYZ
```

## Send Emails Using SMTP

The format of `SMTP_CONFIG` is as follows. **The key must be your own sending domain**, and the value is the SMTP configuration.

For SMTP configuration format details, refer to [zou-yu/worker-mailer](https://github.com/zou-yu/worker-mailer/blob/main/README_zh-CN.md)

> [!warning] Important
> The JSON key (e.g. `your-domain.com` in the example below) must be replaced with **your own domain** — the domain configured in your `DOMAINS` variable.
> This is one of the most common configuration mistakes. Do not copy the example domain directly.

```json
{
    "your-domain.com": {
        "host": "smtp.example.com",
        "port": 465,
        "secure": true,
        "authType": [
            "plain",
            "login"
        ],
        "credentials": {
            "username": "your-smtp-username",
            "password": "your-smtp-password"
        }
    }
}
```

**Field Reference:**

| Field | Description |
|-------|-------------|
| key (e.g. `your-domain.com`) | Your sending domain, must match a domain configured in `DOMAINS` |
| `host` | SMTP server address, e.g. `smtp.mailgun.org`, `smtp.gmail.com`, or your self-hosted SMTP server |
| `port` | SMTP port, typically `465` (SSL) or `587` (STARTTLS) |
| `secure` | Whether to use SSL/TLS. Set to `true` for port 465, `false` for port 587 |
| `authType` | Authentication method, typically `["plain", "login"]` |
| `credentials.username` | SMTP server login username |
| `credentials.password` | SMTP server login password |

If you have **multiple domains** using different SMTP services, add multiple keys in the same JSON:

```json
{
    "domain-a.com": {
        "host": "smtp.mailgun.org",
        "port": 465,
        "secure": true,
        "authType": ["plain", "login"],
        "credentials": { "username": "user@domain-a.com", "password": "xxx" }
    },
    "domain-b.com": {
        "host": "smtp.gmail.com",
        "port": 465,
        "secure": true,
        "authType": ["plain", "login"],
        "credentials": { "username": "user@gmail.com", "password": "app-password" }
    }
}
```

Then execute the following command to add `SMTP_CONFIG` to secrets:

> [!NOTE]
> If you find this troublesome, you can also put it directly in plain text under `[vars]` in `wrangler.toml`, but this is not recommended

If you deployed through the UI, you can add it under `Variables and Secrets` in the Cloudflare UI interface.

```bash
# Switch to worker directory
cd worker
wrangler secret put SMTP_CONFIG
```

## Send Balance Mechanism

Users need a send balance to send emails. The balance mechanism works as follows:

1. **Request Send Permission**: Users must first click the "Request Send Permission" button in the frontend
2. **Default Quota**: Upon requesting, users receive the default quota set by the `DEFAULT_SEND_BALANCE` environment variable (defaults to 0 if not set)
3. **Unlimited Sending**: The following methods can bypass balance checks:
   - Add the address to the "No Limit Send Address List" in the admin console
   - Configure the `NO_LIMIT_SEND_ROLE` environment variable to specify roles that can send without limits

> [!NOTE]
> `DEFAULT_SEND_BALANCE` does **NOT** automatically grant balance to all addresses. Users must actively request send permission first for the quota to take effect.
>
> Layer 1 (`verifiedAddressList` hit) does not deduct balance, but it still counts toward send limits; layers 2/3/4 all deduct balance.
>
> Send limits apply to **all** send channels, including admin send endpoints.
>
> Daily and monthly windows are calculated in **UTC**.
>
> The current limit implementation is a **soft guard**. It is suitable for routine quota control, but it should not be treated as a strict hard-stop cost gate under database errors or high concurrency.

## Send Emails to Authenticated Forwarding Addresses on Cloudflare

Typical use case: non-onboarded domains or Workers free-tier users.

In this compatibility mode, mail is sent via `SEND_MAIL` binding only when the recipient is in the admin `Verified Address List`.
