Migrating from smtp-server
bun-smtp mirrors the smtp-server API. Most servers can switch with only an install change and a single onData adjustment. This page covers exactly what differs.
Install & import
| smtp-server | bun-smtp | |
|---|---|---|
| Install | npm install smtp-server | bun add bun-smtp |
| Import | const { SMTPServer } = require('smtp-server') | import { SMTPServer } from 'bun-smtp' |
Starting the server
server.listen() works the same way as in smtp-server — pass a callback:
// smtp-server
server.listen(2525, callback);
// bun-smtp — identical
server.listen(2525, callback);onData stream
This is the one change most servers will need. smtp-server passes a Node.js Readable; bun-smtp passes a Web ReadableStream<Uint8Array>.
Before (smtp-server):
onData(stream, session, callback) {
const chunks = [];
stream.on("data", (chunk) => chunks.push(chunk));
stream.on("end", () => {
const body = Buffer.concat(chunks);
callback(null);
});
stream.on("error", callback);
}After (bun-smtp):
onData(stream, session, callback) {
async function process() {
const chunks: Uint8Array[] = [];
for await (const chunk of stream) {
chunks.push(chunk);
}
const body = Buffer.concat(chunks);
callback(null);
}
process().catch(callback);
}To discard the body without reading it:
onData(stream, session, callback) {
stream.pipeTo(new WritableStream()).then(() => callback(null), callback);
}stream.byteLength is set after the stream closes. stream.sizeExceeded becomes true as soon as the running byte count exceeds options.size during iteration — you can check it inside a for await loop for early exit. stream.byteLength is only available after the loop completes.
logger option
smtp-server accepts a logger option (bunyan-compatible). bun-smtp does not. Remove it from your constructor options. If you need logging, add it directly in onConnect and onClose.
onSecure socket type
The socket argument in onSecure is a Bun Socket, not a Node.js tls.TLSSocket. TLS details (cipher, protocol version) are available on session.tlsOptions instead.
What stays the same
Everything else is a direct drop-in:
- All constructor options (names, types, defaults)
- Callbacks:
onConnect,onAuth,onMailFrom,onRcptTo,onClose onAuthauth object shape for PLAIN, LOGIN, CRAM-MD5, and XOAUTH2SMTPSessionfieldsSMTPAddressand envelope structureerror.responseCodefor custom SMTP error codes- TLS options:
key,cert,ca,sniOptions, etc. server.close(callback)server.updateSecureContext(options)- Events:
"listening","close","error","connect"