Skip to content

Configure Email Sending

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:

OrderConditionChannelDeducts balance
1SEND_MAIL bound AND recipient in verifiedAddressListCloudflare binding (compat mode)No
2RESEND_TOKEN or RESEND_TOKEN_<DOMAIN> setResend APIYes
3SMTP_CONFIG has entry for current domainworker-mailer SMTPYes
4SEND_MAIL bound (none of the above)Cloudflare binding (recommended primary)Yes
None of the aboveThrows

NOTE

Binding send failures return an error directly.

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

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

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

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:

FieldDescription
key (e.g. your-domain.com)Your sending domain, must match a domain configured in DOMAINS
hostSMTP server address, e.g. smtp.mailgun.org, smtp.gmail.com, or your self-hosted SMTP server
portSMTP port, typically 465 (SSL) or 587 (STARTTLS)
secureWhether to use SSL/TLS. Set to true for port 465, false for port 587
authTypeAuthentication method, typically ["plain", "login"]
credentials.usernameSMTP server login username
credentials.passwordSMTP 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": "[email protected]", "password": "xxx" }
    },
    "domain-b.com": {
        "host": "smtp.gmail.com",
        "port": 465,
        "secure": true,
        "authType": ["plain", "login"],
        "credentials": { "username": "[email protected]", "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. Auto-initialize Default Quota: When DEFAULT_SEND_BALANCE > 0, the system automatically initializes the default quota when the user opens the send page or calls the send-mail API for the first time
  2. Manual Request: If DEFAULT_SEND_BALANCE = 0, users can still click "Request Send Permission" in the frontend to create a pending send-access record for admins to review
  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 only inserts an initial quota for addresses that do not yet have an address_sender row (ON CONFLICT DO NOTHING); existing rows — including admin-disabled or admin-edited ones — are never modified by the runtime path. Restoring a previously disabled or pre-existing address must go through the admin console (enable + set balance).

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.

Based on MIT license