v1.3.3 • Bun recommended, npm supported
Documentation
Shipping SMTP from Cloudflare Workers, cleanly.
The docs below stay close to the published API: install with Bun or npm, configure Workers compatibility, send email, and scale into queues, hooks, and inline assets.
Introduction
What worker-mailer is built to do
worker-mailer is an SMTP client for Cloudflare Workers. It uses TCP sockets, supports typed message envelopes, handles HTML and attachments, and exposes queue helpers for when mail should leave the request path.
Transactional email
Forms, account notifications, and operational emails directly from a Worker.
Queue-backed delivery
Move send work into Cloudflare Queues when throughput or latency matters.
Operational visibility
Hooks, DSN, and explicit errors help with retries and monitoring.
Installation
Install with Bun or npm and enable Worker compatibility
Our recommendation is Bun, especially if the rest of your Worker setup already runs on it. If your team prefers npm, the package install is straightforward there too. Either way, turn on the Worker compatibility flag that the Cloudflare socket runtime needs.
bun add @workermailer/smtpnpm install @workermailer/smtp{
"compatibility_flags": ["nodejs_compat"]
}Quick start
Connect once, or send once
Start with a persistent mailer when you want multiple sends on the same transport, or use the static helper when you just need to fire a single message and return.
import { WorkerMailer } from '@workermailer/smtp'
const mailer = await WorkerMailer.connect({
host: 'smtp.example.com',
port: 465,
secure: true,
authType: 'plain',
credentials: {
username: env.SMTP_USERNAME,
password: env.SMTP_PASSWORD
}
})
await mailer.send({
from: { name: 'Worker Mailer', email: 'noreply@example.com' },
to: { name: 'Alice', email: 'alice@example.com' },
subject: 'Hello from the edge',
text: 'This message was sent from a Cloudflare Worker.',
html: '<h1>Hello from the edge</h1><p>SMTP over TCP sockets.</p>'
})import { WorkerMailer } from '@workermailer/smtp'
await WorkerMailer.send(
{
host: 'smtp.example.com',
port: 465,
secure: true,
credentials: {
username: env.SMTP_USERNAME,
password: env.SMTP_PASSWORD
}
},
{
from: 'noreply@example.com',
to: 'alice@example.com',
subject: 'One-off send',
text: 'No persistent connection required.'
}
)import {
createQueueHandler,
enqueueEmail,
type QueueEmailMessage
} from '@workermailer/smtp/queue'
interface Env {
EMAIL_QUEUE: Queue<QueueEmailMessage>
}
export default {
async fetch(_request: Request, env: Env) {
await enqueueEmail(env.EMAIL_QUEUE, {
mailerOptions: {
host: 'smtp.example.com',
port: 465,
secure: true,
credentials: {
username: env.SMTP_USERNAME,
password: env.SMTP_PASSWORD
}
},
emailOptions: {
from: 'noreply@example.com',
to: 'alice@example.com',
subject: 'Queued email',
text: 'Sent from Cloudflare Queues.'
}
})
return new Response('queued')
},
async queue(batch: MessageBatch<QueueEmailMessage>) {
const handleQueue = createQueueHandler()
await handleQueue(batch)
}
}Providers
SMTP is the documented transport today
worker-mailer ships SMTP. Other provider names appear only in broader ecosystem discussions, so this site does not publish setup code for them.
Configuration
Core connection options
These are the fields you will tune most often in production for connection behavior, auth, delivery status notifications, and timeouts.
| Option | Type | Default | Description |
|---|---|---|---|
| host | string | required | SMTP hostname, such as `smtp.example.com`. |
| port | number | required | SMTP port. Cloudflare Workers commonly pair well with `465` or `587`, depending on your provider. |
| secure | boolean | false | Start the connection over TLS immediately. |
| startTls | boolean | true | Upgrade to TLS when the SMTP server supports it. |
| credentials | { username, password } | undefined | SMTP authentication credentials. |
| authType | 'plain' | 'login' | 'cram-md5' | array | 'plain' | Choose a specific auth mode or provide a preferred order. |
| hooks | WorkerMailerHooks | undefined | Lifecycle hooks for connect, send, error, and close events. |
| dsn | DSN options | undefined | Adds delivery status notifications at the envelope level. |
| socketTimeoutMs | number | runtime default | Socket timeout guard for slow SMTP connections. |
| responseTimeoutMs | number | runtime default | How long to wait for a reply from the server before aborting. |
EmDash discussion
What this site is willing to say today
worker-mailer is relevant to the EmDash email-provider conversation because it already solves SMTP on Cloudflare Workers. Beyond that, the integration surface is still being discussed publicly, so this site avoids publishing guessed implementation details.
- This site documents the current worker-mailer API, not a speculative EmDash plugin API.
- The dedicated EmDash page summarizes the public discussion themes and open questions.
- When the integration direction is final, the docs can grow from there with real examples.
API reference
Transport options and message options
`WorkerMailer.connect()` controls transport behavior. `mailer.send()` controls the message envelope, content, headers, and optional DSN overrides.
type WorkerMailerOptions = {
host: string
port: number
secure?: boolean
startTls?: boolean
credentials?: {
username: string
password: string
}
authType?:
| 'plain'
| 'login'
| 'cram-md5'
| Array<'plain' | 'login' | 'cram-md5'>
hooks?: WorkerMailerHooks
dsn?: {
RET?: { HEADERS?: boolean; FULL?: boolean }
NOTIFY?: { DELAY?: boolean; FAILURE?: boolean; SUCCESS?: boolean }
}
}type EmailOptions = {
from: string | { name?: string; email: string }
to:
| string
| string[]
| { name?: string; email: string }
| Array<{ name?: string; email: string }>
cc?: string | string[]
bcc?: string | string[]
reply?: string | { name?: string; email: string }
subject: string
text?: string
html?: string
headers?: Record<string, string>
attachments?: Attachment[]
dsnOverride?: {
envelopeId?: string
RET?: { HEADERS?: boolean; FULL?: boolean }
NOTIFY?: { DELAY?: boolean; FAILURE?: boolean; SUCCESS?: boolean }
}
}await mailer.send({
from: 'noreply@example.com',
to: 'alice@example.com',
subject: 'Email with inline image',
html: `
<h1>Hello</h1>
<p>Here is the logo:</p>
<img src="cid:company-logo" alt="Company logo" />
`,
attachments: [
{
filename: 'logo.png',
content: env.LOGO_BASE64,
mimeType: 'image/png',
cid: 'company-logo',
inline: true
}
]
})const mailer = await WorkerMailer.connect({
host: 'smtp.example.com',
port: 465,
secure: true,
credentials: {
username: env.SMTP_USERNAME,
password: env.SMTP_PASSWORD
},
hooks: {
onConnect: () => console.log('Connected to SMTP server'),
onSent: (email, response) => console.log('Email sent', email.to, response),
onError: (_email, error) => console.error('Delivery failed', error),
onClose: (error) => console.log('Connection closed', error ?? 'cleanly')
}
})