# Cloudflare Email Sending A Bijection component for durable transactional email sending through Cloudflare Email Service. - Package: @bijectionhq/cloudflare-email-sending - Category: Integrations - Status: Preview - License: Apache-2.0 - Bijection source (repository access required): https://github.com/bijectionhq/cloudflare-email-sending/tree/1f1023a19e5b66a45cb62cc6bc0ca59d6edb06bf - Upstream: https://github.com/ezyyeah/cloudflare-email-sending/tree/ecd56ba4e6da0cb528c44fb45c63bd8ada2fa3b7 - Documentation: https://docs.bijection.com/components/using - Directory: https://bijection.com/components/cloudflare-email-sending ## Get the source Bijection repository access is required. Request access at https://bijection.com/contact. ```sh git clone https://github.com/bijectionhq/cloudflare-email-sending.git cd cloudflare-email-sending git checkout 1f1023a19e5b66a45cb62cc6bc0ca59d6edb06bf ``` ## Before using this component Read the package README at the recorded revision for setup, dependencies, and API examples. Upstream documentation describes the original package and may differ from the Bijection adaptation. This is a source candidate for evaluation. Catalog inclusion does not establish npm availability or runtime qualification. Check compatibility with your SDK and deployment before adoption. External providers run their own compute. ## Upstream package documentation Recorded 2026-10-23. This describes @ezyyeah/cloudflare-email-sending; check the Bijection adaptation separately. A durable email sending component that integrates Convex with Cloudflare Email Service using a queue-first architecture. The component handles transactional emails with built-in retry logic, status tracking, and attachment support. Send requests are queued immediately and processed in the background, with exponential backoff for failed deliveries and idempotency protection for duplicate requests. ### Use cases • Send welcome emails, password reset notifications, and other transactional messages with guaranteed delivery attempts • Build email workflows that need status tracking and cancellation capabilities for user-triggered communications • Handle file attachments from Convex storage or base64 content in receipts, invoices, or document delivery flows • Implement reliable email sending with retry logic for critical notifications where delivery confirmation matters • Replace unreliable direct SMTP calls with a durable queue that survives temporary outages or rate limits ### How it works The component uses a queue-first approach where `send()` immediately stores the email request and returns an ID for status lookup, then schedules background processing. You mount the component in your Convex app config and create a wrapper with your Cloudflare credentials that exposes `send()`, `getStatus()`, and `cancel()` methods. Email validation happens upfront, checking for required content, recipient limits, size constraints, and attachment accessibility. The component resolves Convex storage attachments to base64 before queuing. Failed sends retry with exponential backoff, and idempotency keys prevent duplicate sends. Background actions handle the actual Cloudflare API calls using your account credentials. Status tracking covers the full lifecycle from `queued` through `processed` or `failed`, though it focuses on acceptance-level confirmation rather than final inbox delivery. ### Upstream README # @ezyyeah/cloudflare-email-sending Durable transactional email sending for Convex through Cloudflare Email Service. This package is queue-first: `send()` stores the request in the component, schedules background work, and returns a component-generated email id for later status lookup. Cloudflare Email Service is currently in beta. Features, limits, and API behavior may still change before general availability. ## Install ```bash npm install @ezyyeah/cloudflare-email-sending ``` Mount the component in your Convex app config: ```ts import cloudflareEmail from "@ezyyeah/cloudflare-email-sending/convex.config.js"; import { defineApp } from "convex/server"; const app = defineApp(); app.use(cloudflareEmail); export default app; ``` ## Cloudflare Setup Before sending email, make sure your Cloudflare account is ready: 1. Add your sending domain to Cloudflare and use Cloudflare DNS for it. 2. Open `Email Sending` in the Cloudflare dashboard. 3. Onboard the domain you want to send from. 4. Wait for the DNS records Cloudflare adds for SPF, DKIM, DMARC, and bounce handling to propagate. Cloudflare may initially limit new accounts to sending only to verified recipient addresses. ## Convex Environment Variables Set these in your Convex app deployment: - `CLOUDFLARE_EMAIL_ACCOUNT_ID` - `CLOUDFLARE_EMAIL_API_TOKEN` Optional: - `CLOUDFLARE_EMAIL_API_BASE_URL` Example: ```bash npx convex env set CLOUDFLARE_EMAIL_ACCOUNT_ID your-account-id npx convex env set CLOUDFLARE_EMAIL_API_TOKEN your-api-token ``` These env vars belong in the app deployment, not inside the component. The wrapper reads them in the app runtime and forwards the resolved provider config into the isolated component. ## How To Get The Cloudflare Values ### `CLOUDFLARE_EMAIL_ACCOUNT_ID` You can copy the account ID from either of these places in Cloudflare: 1. `Account Home` 2. Open the menu next to the account 3. Click `Copy account ID` Or: 1. `Workers & Pages` 2. Find the `Account details` section 3. Copy the `Account ID` ### `CLOUDFLARE_EMAIL_API_TOKEN` Create a Cloudflare API token for the same account: 1. Open `My Profile > API Tokens` for a user token, or `Manage Account > API Tokens` for an account token. 2. Click `Create Token`. 3. Create a custom token. 4. Restrict it to the Cloudflare account you onboarded for Email Sending. 5. Give it permission to send emails. 6. Create the token and copy the secret immediately. Cloudflare only shows the token secret once, so store it securely. ## Client Wrapper ```ts import { CloudflareEmail } from "@ezyyeah/cloudflare-email-sending"; import { components } from "./_generated/api"; export const email = new CloudflareEmail(components.cloudflareEmail, { accountId: process.env.CLOUDFLARE_EMAIL_ACCOUNT_ID, apiToken: process.env.CLOUDFLARE_EMAIL_API_TOKEN, initialBackoffMs: 5_000, maxAttempts: 7, maxBackoffMs: 60_000, apiBaseUrl: "https://api.cloudflare.com/client/v4", }); ``` Supported constructor options: - `accountId` - `apiToken` - `apiBaseUrl` - `initialBackoffMs` - `maxAttempts` - `maxBackoffMs` Defaults: - `apiBaseUrl`: `https://api.cloudflare.com/client/v4` - `initialBackoffMs`: `30_000` - `maxAttempts`: `5` - `maxBackoffMs`: `1_800_000` If you omit `accountId` and `apiToken` in the constructor, the wrapper reads `CLOUDFLARE_EMAIL_ACCOUNT_ID` and `CLOUDFLARE_EMAIL_API_TOKEN` from the app runtime at send time. ## Usage `send()` expects an action context with `runAction`. If you use storage-backed attachments, the same context must also expose `storage.get()` because the wrapper resolves the file before calling the component. ```ts import { action } from "./_generated/server"; import { email } from "./email"; export const sendWelcomeEmail = action({ args: {}, handler: async (ctx) => { return await email.send(ctx, { from: { address: "welcome@example.com", name: "Acme" }, to: "user@example.com", subject: "Welcome", html: "