Wraps Logo
Quickstart / Cloudflare Workers

Send Email from Cloudflare Workers

Deploy email infrastructure to your AWS account and send your first email from a Cloudflare Worker running at the edge.

Prerequisites

Before you begin, make sure you have:

  • Node.js 18 or later installed
  • A Cloudflare account with Wrangler installed
  • AWS credentials configured (AWS Setup Guide)
  • A domain you own

1
Deploy Infrastructure

Run the Wraps CLI to deploy email infrastructure to your AWS account:

GNU Bashterminal.sh
npx @wraps.dev/cli email init

2
Add Your Domain

Add and verify your sending domain with AWS SES:

GNU Bashterminal.sh
npx @wraps.dev/cli email domains add -d yourdomain.com

DNS Setup

The CLI will output DKIM records to add to your DNS provider. Once added, verify them with npx @wraps.dev/cli email domains verify -d yourdomain.com

3
Create a Worker and Install the SDK

Scaffold a new Worker if you don't have one, then install the @wraps.dev/email package:

GNU Bashterminal.sh
npm create cloudflare@latest email-worker -- --type hello-world
GNU Bashterminal.sh
cd email-worker && npm install @wraps.dev/email

4
Configure Wrangler

The @wraps.dev/email/workers entry uses Web Crypto and fetch— no Node.js APIs — so you don't need nodejs_compat. Just set a region variable in your Wrangler config:

wrangler.jsonc
{  "name": "email-worker",  "main": "src/index.ts",  "compatibility_date": "2026-06-16",  "vars": {    "AWS_REGION": "us-east-1"  }}

No Node.js Compat Needed

The /workers entry is self-contained (~5 KB bundled) and runs on plain workerd with no polyfills. Drop the nodejs_compat flag entirely — it's not required and adds unnecessary overhead.

5
Store AWS Credentials as Secrets

Workers have no filesystem and no AWS credential chain, so you can't rely on ~/.aws or instance metadata. Store your IAM keys as encrypted Wrangler secrets:

GNU Bashterminal.sh
npx wrangler secret put AWS_ACCESS_KEY_ID
GNU Bashterminal.sh
npx wrangler secret put AWS_SECRET_ACCESS_KEY

Local Development

For wrangler dev, put the same keys in a .dev.vars file (and add it to .gitignore). Scope the IAM user to ses:SendEmail only.

6
Send Email from the Worker

Build a WrapsEmail instance inside the fetch handler, passing credentials from the env binding. The handler below accepts a JSON body and returns the SES message ID:

TypeScriptsrc/index.ts
import { SESError, ValidationError, WrapsEmail } from '@wraps.dev/email/workers';type Env = {  AWS_ACCESS_KEY_ID: string;  AWS_SECRET_ACCESS_KEY: string;  AWS_REGION: string;};export default {  async fetch(request: Request, env: Env): Promise<Response> {    // Instantiate per request — `env` isn't available at module scope.    // Workers have no AWS credential chain (no filesystem, no ~/.aws,    // no instance metadata), so pass credentials explicitly from the env    // binding — Wrangler secrets for the keys, a var for the region.    const email = new WrapsEmail({      region: env.AWS_REGION,      credentials: {        accessKeyId: env.AWS_ACCESS_KEY_ID,        secretAccessKey: env.AWS_SECRET_ACCESS_KEY,      },    });    try {      const { to, subject, html } = await request.json<{        to: string;        subject: string;        html: string;      }>();      const result = await email.send({        from: 'hello@yourdomain.com',        to,        subject,        html,      });      return Response.json({ success: true, messageId: result.messageId });    } catch (error) {      if (error instanceof ValidationError) {        return Response.json({ error: error.message }, { status: 400 });      }      if (error instanceof SESError) {        return Response.json(          { error: error.message, code: error.code },          { status: error.retryable ? 503 : 400 },        );      }      throw error;    }  },};

Error Types

  • ValidationError — Invalid email parameters (missing fields, bad format)
  • SESError — AWS SES errors (throttling, bounces, invalid identity); check retryable to decide whether to retry

7
Deploy

Ship the Worker to Cloudflare's edge network:

GNU Bashterminal.sh
npx wrangler deploy

Send a test request to your Worker URL:

GNU Bashterminal.sh
curl -X POST https://email-worker.<your-subdomain>.workers.dev \  -H "Content-Type: application/json" \  -d '{"to":"you@example.com","subject":"Hello from the edge","html":"<h1>It works</h1>"}'

Next Steps

Email SDK Reference

Learn about all available methods, options, and advanced features.

View SDK Docs
Error Codes

Reference for all error codes and troubleshooting steps.

View Errors

Need Help?

If you run into any issues, check our GitHub discussions or open an issue.

Get Help