DEV Community

Cover image for How to send email from Cloudflare Workers, Deno, and Bun (Nodemailer won't)
Ali AlNaghmousgh
Ali AlNaghmousgh

Posted on

How to send email from Cloudflare Workers, Deno, and Bun (Nodemailer won't)

If you've tried sending email from a Cloudflare Worker, a Deno app, or a Bun server, you've probably hit the same wall I did: Nodemailer doesn't run there.

Nodemailer has been the default for ~15 years, and it's excellent — but it's built on Node's net/tls/stream built-ins. On the edge and on non-Node runtimes, those simply don't exist. You end up reaching for a provider's proprietary SDK per environment, or giving up on a unified API.

So I built sently — a runtime-agnostic email library that runs anywhere JavaScript runs.

Nodemailer vs sently

Nodemailer sently
Runtimes Node only Node, Bun, Deno, CF Workers
Module format CommonJS ESM only
Bundle size ~58 KB always ~6 KB HTTP · ~15 KB SMTP
HTTP transports via plugins 6 built-in
React Email via plugin built-in
Idempotency
Webhook parsing
TypeScript via @types built-in

The idea

One API, every runtime. sently uses only Web APIs (fetch, Web Crypto, Streams) in its core, so the same code sends mail on Node, Bun, Deno, and Cloudflare Workers. No Node built-ins in the hot path.

Sending via an HTTP provider

Most edge use cases use an HTTP API like Resend or SendGrid. Here's the whole thing:

import { createMailer } from "sently/mailer";
import { ResendTransport } from "sently/transports/resend";

const mailer = await createMailer({
  transport: new ResendTransport({ apiKey: env.RESEND_API_KEY }),
});

await mailer.send({
  from: "you@yourdomain.com",
  to: "user@example.com",
  subject: "Hello from the edge",
  html: "<p>Sent from a Worker.</p>",
});
Enter fullscreen mode Exit fullscreen mode

That exact code runs unmodified in a Cloudflare Worker, a Deno deploy, a Bun server, or plain Node. You only change the transport if you want a different provider.

Why it stays small

sently ships as subpath exports, so you only bundle what you import. A complete HTTP send path (mailer + Resend) is about 6 KB gzip.

For comparison, Nodemailer is ~58 KB min+gzip (measured on Bundlephobia) — and Node-only. So on top of running where Nodemailer can't, the HTTP path is roughly 10× smaller.

SMTP, DKIM, webhooks, idempotency, React Email rendering — all opt-in, none pulled in unless you import them.

Need SMTP instead?

HTTP providers are the common edge case, but classic SMTP relays work too — with pooling, STARTTLS, and DKIM:

import { createSMTPMailer } from "sently";

const mailer = await createSMTPMailer({
  host: "smtp.example.com",
  port: 587,
  auth: { user: "you@example.com", pass: process.env.SMTP_PASS },
});

await mailer.send({
  from: "you@example.com",
  to: "user@example.com",
  subject: "Hello",
  html: "<p>Sent over SMTP.</p>",
});
Enter fullscreen mode Exit fullscreen mode

(SMTP needs raw sockets, so it runs on Node, Bun, and Deno — not inside a Worker, which has no socket access. The HTTP transports above are the edge path.)

A nice local-dev touch

In development you can write emails to disk instead of sending them — no test inbox needed:

import { createMailer } from "sently/mailer";
import { PreviewTransport } from "sently/transports/preview";

const mailer = await createMailer({
  transport: new PreviewTransport({ outDir: ".emails", open: true }),
});
Enter fullscreen mode Exit fullscreen mode

What's in it

  • HTTP transports: Resend, SendGrid, Postmark, Mailgun, AWS SES, Brevo
  • SMTP: STARTTLS/TLS, pooling, DKIM (RSA + Ed25519), OAuth2 (Gmail + Microsoft 365)
  • React Email rendering
  • Idempotency — dedupe on retry/replay (handy for billing/notification emails)
  • Bulk send with native provider batch endpoints
  • Webhook parsing for all six providers, with constant-time signature verification
  • ESM-native, tree-shakeable, TypeScript-first

Try it

bun add sently   # npm / pnpm / deno / jsr too
Enter fullscreen mode Exit fullscreen mode

It's pre-1.0 but stable — pin an exact version for production. I built it for my own projects and opened it up; feedback and issues are very welcome.

🔗 github.com/alialnaghmoush/sently


What are you using to send email from the edge right now? Curious whether others have hit the same Nodemailer wall — let me know in the comments.

Top comments (0)