Cloudflare Email
Send emails via the Cloudflare Email Service REST API
The Cloudflare Email transport is an official Better-Notify package that sends email through the Cloudflare Email Service REST API. Use it when you are already on Cloudflare and want transactional email without an external provider.
It uses plain fetch() with zero external dependencies, so it works in Node.js, Bun, Cloudflare Workers, and any runtime with a global fetch.
Cloudflare Email Service is currently in beta and requires a Workers Paid plan.
Install
npm install @betternotify/cloudflare-email @betternotify/core @betternotify/emailGetting your credentials
You need two values: an API token and your account ID.
API token
- Go to API Tokens in your Cloudflare dashboard.
- Click Create Token.
- Select Create Custom Token.
- Under Permissions, set Account > Email Sending > Edit.

- Click Continue to summary, then Create Token.
- Copy the token — you will not see it again.
Account ID
- Go to your Cloudflare dashboard home page.
- Click the three-dot menu (⋮) next to your account name.
- Select Copy account ID.

Store both values as environment variables:
CF_ACCOUNT_ID=your-account-id
CF_API_TOKEN=your-api-tokenUsage
import { createNotify, createClient } from '@betternotify/core';
import { emailChannel } from '@betternotify/email';
import { cloudflareEmailTransport } from '@betternotify/cloudflare-email';
const email = emailChannel({
defaults: { from: { name: 'My App', email: 'noreply@example.com' } },
});
const rpc = createNotify({ channels: { email } });
const catalog = rpc.catalog({
/* routes */
});
const mail = createClient({
catalog,
transportsByChannel: {
email: cloudflareEmailTransport({
accountId: process.env.CF_ACCOUNT_ID!,
apiToken: process.env.CF_API_TOKEN!,
}),
},
});Options
Prop
Type
Error handling
The transport maps Cloudflare error codes to Better-Notify error codes:
| Cloudflare code | Meaning | Better-Notify code |
|---|---|---|
| 10001 | Invalid request schema | VALIDATION |
| 10200 | Invalid email content | VALIDATION |
| 10201 | Missing content length | VALIDATION |
| 10202 | Message too large | VALIDATION |
| 10203 | Sending disabled | CONFIG |
| 10004 | Rate limited | PROVIDER |
| 10002 | Internal server error | PROVIDER |
Network failures and unparseable responses are wrapped as PROVIDER errors.
Attachments
The transport base64-encodes attachment content and infers the disposition from the cid field:
- If
cidis set, the attachment is sent asinlinewith acontentId. - Otherwise it is sent as
attachment. - If
contentTypeis not set, it defaults toapplication/octet-stream.
Limits
These limits are enforced by Cloudflare, not by the transport:
- 50 recipients per email (to + cc + bcc combined)
- 5 MiB total message size (25 MiB for verified addresses)
- 998 characters subject line (RFC 5322)
- 16 KB custom headers
- Daily sending limits vary by account standing
If you need throttling or provider fallback, add withRateLimit middleware or wrap the transport in multiTransport({ strategy: 'failover' }).