# Cloudflare Workers Email Quickstart

Source: https://wraps.dev/docs/quickstart/email/cloudflare

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.

Run this quickstart with your AI agent

Paste into Claude Code, Cursor, or any agent with shell access.

Copy promptView

Using Claude Code? Install the Wraps skills for deeper context:

npx add-skill wraps-team/skills

Prerequisites

Before you begin, make sure you have:

-   Node.js 20 or later installed
-   A Cloudflare account with Wrangler installed
-   AWS credentials configured ([AWS Setup Guide](https://wraps.dev/docs/guides/aws-setup))
-   A domain you own

## 

1

Deploy Infrastructure

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

terminal.sh

```
npx @wraps.dev/cli email init
```

## 

2

Add Your Domain

Add and verify your sending domain with AWS SES:

terminal.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:

terminal.sh

```
npm create cloudflare@latest email-worker -- --type hello-world
```

terminal.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:

terminal.sh

```
npx wrangler secret put AWS_ACCESS_KEY_ID
```

terminal.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:

src/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:

terminal.sh

```
npx wrangler deploy
```

Send a test request to your Worker URL:

terminal.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](https://wraps.dev/docs/sdk-reference)

Error Codes

Reference for all error codes and troubleshooting steps.

[View Errors](https://wraps.dev/docs/reference/errors)

### Need Help?

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

[Get Help](https://github.com/wraps-team/wraps/discussions)
