Skip to content

Find a component

Loading the directory…

↑ ↓ to select · Enter to open · Esc to closeBrowse all →
Contributor login

Cloudflare Email Sending

A Bijection component for durable transactional email sending through Cloudflare Email Service.

Bijection source
git clone https://github.com/bijectionhq/cloudflare-email-sending.git
cd cloudflare-email-sending
git checkout 1f1023a19e5b66a45cb62cc6bc0ca59d6edb06bf

Bijection repository access is required. Request access.

Description

A Bijection component for durable transactional email sending through Cloudflare Email Service.

Explore the package README for API concepts, configuration, and examples. Check the Bijection source for adaptation-specific setup.

Available to evaluate

Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.

Getting started

  1. Get the source at the revision above and read its package documentation.
  2. Check the required Bijection SDK version, dependencies, and external service credentials.
  3. Follow the package setup instructions and the guide to using components.
  4. Exercise the component in your deployment according to your project’s verification rules.

Open source

Maintained source: bijectionhq/cloudflare-email-sending. Based on ezyyeah/cloudflare-email-sending. License: Apache-2.0. Consult the repository for license terms, notices, and contributor attribution.

From the upstream publisher

A Convex component for durable transactional email sending through Cloudflare Email Service.

Explore the demo ↗

Upstream overview

From the upstream package documentation.

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

From the upstream package documentation.

  • 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

From the upstream package documentation.

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 package FAQ

What Cloudflare setup is required before using this component?

The Cloudflare Email Sending component requires your domain added to Cloudflare DNS, onboarding through the Email Sending dashboard, and waiting for SPF, DKIM, and DMARC DNS propagation. You also need a Cloudflare account ID and API token configured in Convex environment variables.

Does this component guarantee email delivery to inboxes?

No, the Cloudflare Email Sending component provides acceptance-level tracking only. The 'processed' status means Cloudflare accepted the request, not that the email reached the recipient's inbox. Webhook-driven delivery tracking is not currently implemented.

How does retry logic work for failed emails?

The Cloudflare Email Sending component uses exponential backoff with deterministic jitter for retryable failures including network errors, 429 rate limits, and 5xx server errors. Default configuration attempts 5 retries with backoff from 30 seconds to 30 minutes maximum.

Can I send bulk or marketing emails with this component?

The Cloudflare Email Sending component is designed for transactional emails only. It enforces a maximum of 50 recipients per message and focuses on reliable delivery of individual user-triggered emails rather than bulk marketing campaigns.

What file types can be attached to emails?

The Cloudflare Email Sending component supports any file type as attachments, either from Convex storage IDs or base64 encoded content. It validates the total message size stays within Cloudflare's 25 MiB limit and rejects empty filenames or unreadable storage references.

Use with agents and CLI

Give your coding agent the component’s source identity and setup guidance.

agent-prompt.md
Help me integrate Cloudflare Email Sending with Bijection.

Read: https://bijection.com/components/cloudflare-email-sending/SKILL.md

Check SDK compatibility and dependency availability.
Follow this project's execution and verification rules.

Upstream README.md

View repository ↗

Documentation from ezyyeah/cloudflare-email-sending, recorded October 23, 2026. APIs and setup may differ in the Bijection adaptation.

@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

npm install @ezyyeah/cloudflare-email-sending

Mount the component in your Convex app config:

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:

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

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.

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: "<h1>Welcome</h1>",
      text: "Welcome",
      headers: {
        "X-Campaign-ID": "welcome-2026-04",
      },
      idempotencyKey: "user_123:welcome",
    });
  },
});

Attachment inputs support either inline base64 content or app-storage resolution:

await email.send(ctx, {
  from: "ops@example.com",
  to: "user@example.com",
  subject: "Receipt",
  text: "Attached.",
  attachments: [
    {
      filename: "receipt.pdf",
      storageId: receiptStorageId,
      type: "application/pdf",
    },
    {
      filename: "terms.txt",
      content: Buffer.from("hello").toString("base64"),
      type: "text/plain",
    },
  ],
});

Status lookup and cancellation use the wrapper against Convex query and mutation contexts:

const status = await email.getStatus(ctx, emailId);
const state = await email.cancel(ctx, emailId);

Public wrapper methods:

  • send(ctx, args): Promise<EmailId>
  • getStatus(ctx, emailId): Promise<EmailStatus<EmailId> | null>
  • cancel(ctx, emailId): Promise<EmailState | null>

Send Contract

send() validates the request before enqueueing:

  • At least one of html or text is required.
  • A maximum of 50 total recipients is allowed across to, cc, and bcc.
  • Estimated message size must stay within Cloudflare's 25 MiB limit.
  • Custom headers are validated locally and platform-controlled headers are rejected.
  • Empty attachment filenames and unreadable storageId attachments are rejected before dispatch.

If you reuse an idempotencyKey with the same normalized payload, the component returns the existing EmailId. Reusing the same key with a different payload throws IdempotencyConflictError.

Status Semantics

getStatus() returns one of these states:

  • queued
  • sending
  • retrying
  • processed
  • needs_reconciliation
  • failed
  • cancelled

processed means Cloudflare accepted the send request and returned immediate recipient outcomes. It does not mean final inbox delivery was confirmed.

needs_reconciliation means Cloudflare accepted the request, but local bookkeeping failed afterwards. Treat that state as terminal until you inspect the stored record and decide whether to resend manually.

Operational Notes

  • Provider credentials are resolved in the app runtime, then persisted with the queued email so background work can run inside the isolated component.
  • Retries use exponential backoff with deterministic jitter for retryable network, 429, and 5xx failures.
  • Status is acceptance-level only. Cloudflare webhook or downstream lifecycle tracking is not implemented here yet.
  • This package is intended for transactional email flows. It does not position Cloudflare Email Service as a bulk or marketing transport.

Current Scope

This package focuses on outbound sending.

It does not currently include:

  • inbound routing or receiving
  • webhook-driven delivery lifecycle updates
  • analytics or suppression tooling
  • component-owned uploads
  • remote URL attachment fetching
  • multi-account credential routing

Package Exports

  • @ezyyeah/cloudflare-email-sending
  • @ezyyeah/cloudflare-email-sending/convex.config.js
  • @ezyyeah/cloudflare-email-sending/test

Local Example

The repository includes a runnable React + Next.js example app in example.

From the repository root:

pnpm install
pnpm dev

That starts:

Optional provider setup for actual delivery:

cd example
npx convex env set CLOUDFLARE_EMAIL_ACCOUNT_ID your-account-id
npx convex env set CLOUDFLARE_EMAIL_API_TOKEN your-api-token

Without those env vars in the app runtime, the example still runs, but send attempts will fail before provider dispatch.

References

Keywords