How to Send Emails from Cloudflare Workers (2026 Guide)

Nik@nikpolale
10 min read
Cloudflare Workers run at the edge with the Web Standards API. Email sending is a fetch call to your provider's API. No npm packages needed for basic sending, though you can use them with Workers bundler.
This guide covers Workers email sending, environment bindings, and scheduled triggers.
Create a Worker
npm create cloudflare@latest -- send-email-worker
cd send-email-workerEmail Sending Worker
src/index.ts
interface Env {
SEQUENZY_API_KEY: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
const url = new URL(request.url);
if (url.pathname === "/api/send-welcome") {
const { email, name } = await request.json<{ email: string; name: string }>();
if (!email || !name) {
return Response.json({ error: "email and name required" }, { status: 400 });
}
const response = await fetch("https://api.sequenzy.com/v1/transactional/send", {
method: "POST",
headers: {
"Authorization": `Bearer ${env.SEQUENZY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
to: email,
subject: `Welcome, ${name}`,
body: `<h1>Welcome, ${name}</h1><p>Your account is ready.</p>`,
}),
});
if (!response.ok) {
return Response.json({ error: "Failed to send" }, { status: 500 });
}
return Response.json(await response.json());
}
return new Response("Not found", { status: 404 });
},
};src/index.ts
interface Env {
RESEND_API_KEY: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
const url = new URL(request.url);
if (url.pathname === "/api/send-welcome") {
const { email, name } = await request.json<{ email: string; name: string }>();
if (!email || !name) {
return Response.json({ error: "email and name required" }, { status: 400 });
}
const response = await fetch("https://api.resend.com/emails", {
method: "POST",
headers: {
"Authorization": `Bearer ${env.RESEND_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "Your App <noreply@yourdomain.com>",
to: email,
subject: `Welcome, ${name}`,
html: `<h1>Welcome, ${name}</h1><p>Your account is ready.</p>`,
}),
});
if (!response.ok) {
return Response.json({ error: "Failed to send" }, { status: 500 });
}
return Response.json(await response.json());
}
return new Response("Not found", { status: 404 });
},
};src/index.ts
interface Env {
SENDGRID_API_KEY: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
const url = new URL(request.url);
if (url.pathname === "/api/send-welcome") {
const { email, name } = await request.json<{ email: string; name: string }>();
if (!email || !name) {
return Response.json({ error: "email and name required" }, { status: 400 });
}
const response = await fetch("https://api.sendgrid.com/v3/mail/send", {
method: "POST",
headers: {
"Authorization": `Bearer ${env.SENDGRID_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
personalizations: [{ to: [{ email }] }],
from: { email: "noreply@yourdomain.com" },
subject: `Welcome, ${name}`,
content: [{ type: "text/html", value: `<h1>Welcome, ${name}</h1><p>Your account is ready.</p>` }],
}),
});
if (!response.ok) {
return Response.json({ error: "Failed to send" }, { status: 500 });
}
return Response.json({ sent: true });
}
return new Response("Not found", { status: 404 });
},
};Set Secrets
wrangler secret put SEQUENZY_API_KEYScheduled Emails (Cron Triggers)
Send emails on a schedule using Workers Cron Triggers:
// src/index.ts
interface Env {
SEQUENZY_API_KEY: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// ... HTTP handler
},
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) {
// Runs on schedule - e.g., send a daily digest
await fetch("https://api.sequenzy.com/v1/transactional/send", {
method: "POST",
headers: {
"Authorization": `Bearer ${env.SEQUENZY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
to: "team@yourcompany.com",
subject: "Daily Report",
body: "<h1>Daily Report</h1><p>Here are today's metrics...</p>",
}),
});
},
};# wrangler.toml
[triggers]
crons = ["0 9 * * *"] # Every day at 9am UTCDeploy
wrangler deployGoing to Production
1. Verify Your Domain
Add SPF, DKIM, DMARC DNS records.
2. Use Secrets for API Keys
wrangler secret put SEQUENZY_API_KEYNever put API keys in wrangler.toml.
3. Use ctx.waitUntil for Background Work
ctx.waitUntil(sendEmailInBackground(env));This lets the response return immediately while the email sends in the background.
Beyond Transactional
Sequenzy handles transactional sends, marketing campaigns, automated sequences, and subscriber management from one API. Native Stripe integration for SaaS.
Wrapping Up
- Fetch API for zero-dependency email sending
- Environment bindings for secure API key access
- Cron triggers for scheduled emails
- Edge deployment for low-latency responses
Pick your provider, deploy a Worker, and start sending.