Skip to content

hono-email/smtp provides a connector-based SMTP sender. You choose a runtime connector, instantiate SmtpTransport, and pass it to sendEmail().

import CloudflareConnector from 'hono-email/smtp/cloudflare'
import { Body, Html, Text, sendEmail } from 'hono-email'
import { SmtpTransport } from 'hono-email/smtp'
const dkimPrivateKey = `-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----`
const smtp = new SmtpTransport({
connector: CloudflareConnector,
hostname: 'smtp.example.com',
port: 587,
secure: 'starttls',
connectionTimeout: 10_000,
greetingTimeout: 10_000,
socketTimeout: 30_000,
auth: {
username: 'smtp-user',
password: 'smtp-password',
},
dkim: {
domainName: 'example.com',
keySelector: 'mail',
privateKey: dkimPrivateKey,
},
pool: {
maxConnections: 2,
maxMessages: 100,
},
limits: {
maxAttachmentSize: 10 * 1024 * 1024,
},
})
try {
await smtp.verify()
const receipt = await sendEmail({
adapter: smtp,
from: 'sender@example.com',
to: ['recipient@example.com', 'second@example.com'],
subject: 'Welcome',
envelope: {
from: 'bounces@example.com',
},
jsx: (
<Html>
<Body>
<Text>Hello from hono-email.</Text>
</Body>
</Html>
),
attachments: [
{
filename: 'invoice.txt',
content: 'Invoice text',
contentType: 'text/plain',
},
{
filename: 'logo.png',
href: 'https://example.com/assets/logo.png',
cid: 'logo',
contentDisposition: 'inline',
},
],
})
if (!receipt.successful) {
console.error(receipt.errorMessages)
}
} finally {
await smtp.close()
}

For the full list of configuration options, see the SMTP Options API Reference.

Entry point Runtime
hono-email/smtp/cloudflare Cloudflare Workers
hono-email/smtp/node Node.js
hono-email/smtp/deno Deno
hono-email/smtp/bun Bun
  • SMTP transport uses Web Streams internally. Runtime-specific socket support is supplied by the connector.
  • Cloudflare Workers does not allow outbound SMTP connections on port 25. Use submission ports such as 465 or 587.
  • Bun’s TCP API supports direct TLS connections, but the Bun connector does not support STARTTLS upgrade. Use port 465 with secure: true on Bun.

SmtpTransport reuses SMTP sessions until smtp.close() is called. The default pool size is 1, so sends on the same transport share one TCP connection sequentially.

  • Set pool.maxConnections to allow multiple concurrent SMTP sessions.
  • Set pool.maxMessages to retire a session after a fixed number of messages.
  • If a session fails during send, that session is discarded. The message is not retried automatically.
  • await smtp.verify() checks connection setup, TLS negotiation, and authentication without sending a message.
  • dkim can be configured on SmtpTransport or overridden per message to add a DKIM-Signature header before SMTP delivery.
  • envelope lets you override the SMTP envelope sender and recipients without changing the visible From / To headers.
  • to, cc, bcc, and envelope.to accept single addresses or arrays. SMTP sends one RCPT TO command per resolved recipient and reports partial recipient rejection in receipt.rejected.

Configure SmtpTransportOptions.dkim for default signing settings. For per-send signing, pass SmtpSendOptions to smtp.send(). Load privateKey from your application’s secret store and use a message returned by renderEmailMessage():

import type { SmtpSendOptions } from 'hono-email/smtp'
const options: SmtpSendOptions = {
dkim: { domainName: 'example.com', keySelector: 'mail', privateKey },
}
const receipt = await smtp.send(message, options)

DKIM settings are selected in this order, without merging configurations:

  1. smtp.send(message, { dkim }) for the current send.
  2. Legacy message.dkim.
  3. The transport’s dkim default.

Omitting per-send dkim inherits the legacy or transport setting. false is not supported; signing is disabled only when no DKIM configuration is present. Per-send options do not mutate the message or change defaults for later sends.

EmailMessage.dkim is deprecated but remains supported in EmailMessageDraft and sendEmail() options. Migrate smtp.send({ ...message, dkim }) to smtp.send(message, { dkim }). For JSX drafts passed to sendEmail({ adapter: smtp, ..., dkim }), move shared settings into the transport constructor, or render with renderEmailMessage() from hono-email/adapter and pass per-send settings to smtp.send(). The shared sendEmail() helper uses the transport defaults and legacy message settings; it does not accept SmtpSendOptions.

Provider adapters cannot apply locally supplied DKIM settings. Configure DKIM with the provider. SmtpTransport used with the Cloudflare SMTP connector signs locally, like other SMTP connectors.

Type: SmtpConnector

Runtime-specific TCP socket connector. Import one for your runtime:

Entry point Runtime
hono-email/smtp/node Node.js
hono-email/smtp/bun Bun
hono-email/smtp/deno Deno
hono-email/smtp/cloudflare Cloudflare Workers

Type: string

SMTP server hostname.

Type: number

SMTP server port. Use 587 for STARTTLS submission, 465 for implicit TLS.

Type: boolean | 'starttls' · Default: 'starttls'

TLS mode:

Value Behavior
'starttls' Upgrade to TLS after connecting (STARTTLS).
true Implicit TLS from the start of the connection.
false Plain-text connection with no TLS.

Type: { type?: 'plain' | 'login'; username: string; password: string }

SMTP authentication credentials. type defaults to 'plain'.

Type: EmailDkimOptions

DKIM signing options. See Email Types.

Type: string

Hostname sent in the EHLO/HELO greeting. Defaults to the connector’s resolved hostname.

connectionTimeout Optional

Section titled “connectionTimeout ”

Type: number

Milliseconds to wait for the TCP connection to be established.

Type: number

Milliseconds to wait for the server greeting after connecting.

Type: number

Milliseconds of inactivity before the socket is considered timed out.

Type: { maxConnections?: number; maxMessages?: number }

Connection pool settings.

  • maxConnections — maximum concurrent SMTP sessions. Default: 1.
  • maxMessages — retire a session after this many messages. Default: unlimited.

limits.maxAttachmentSize Optional

Section titled “limits.maxAttachmentSize ”

Type: number

Maximum attachment size in bytes.