Documentation Index Fetch the complete documentation index at: https://mintlify.com/puiusabin/bun-smtp/llms.txt
Use this file to discover all available pages before exploring further.
This example shows how to create a basic SMTP server that accepts emails without authentication and saves them to disk.
Complete example
import { SMTPServer } from "bun-smtp" ;
import type { DataStream , SMTPSession } from "bun-smtp" ;
const server = new SMTPServer ({
authOptional: true ,
onData ( stream : DataStream , session : SMTPSession , callback ) {
async function saveEmail () {
const chunks : Uint8Array [] = [];
// Collect all chunks from the stream
for await ( const chunk of stream ) {
chunks . push ( chunk );
}
// Combine chunks into a single buffer
const emailContent = Buffer . concat ( chunks );
// Save to disk with timestamp
const filename = `email- ${ Date . now () } .eml` ;
await Bun . write ( filename , emailContent );
console . log ( `Saved email to ${ filename } ` );
console . log ( `From: ${ session . envelope . mailFrom ?. address } ` );
console . log ( `To: ${ session . envelope . rcptTo . map ( r => r . address ). join ( ", " ) } ` );
callback ( null );
}
saveEmail (). catch ( callback );
},
});
await server . listen ( 2525 );
console . log ( "SMTP server listening on port 2525" );
How it works
Configure the server
Set authOptional: true to allow clients to send mail without authenticating.
Handle incoming data
The onData callback receives a ReadableStream<Uint8Array> containing the email message.
Process the stream
Use a for await...of loop to consume all chunks from the stream.
Save the email
Use Bun.write() to save the combined buffer to disk as a .eml file.
Complete the transaction
Call callback(null) to send a success response to the client.
You must fully consume the stream before calling the callback. Otherwise, the client will hang waiting for a response.
Testing the server
Send a test email using the mail command or telnet:
Then type:
HELO localhost
MAIL FROM:<sender@example.com>
RCPT TO:<recipient@example.com>
DATA
Subject: Test Email
This is a test message.
.
QUIT
The session.envelope object contains metadata about the email:
onData ( stream , session , callback ) {
console . log ( "Sender:" , session . envelope . mailFrom ?. address );
console . log ( "Recipients:" , session . envelope . rcptTo . map ( r => r . address ));
console . log ( "Body type:" , session . envelope . bodyType ); // "7bit" or "8bitmime"
console . log ( "UTF8:" , session . envelope . smtpUtf8 );
console . log ( "Require TLS:" , session . envelope . requireTLS );
// ... process stream
}
Error handling
Reject the email with a custom error code:
onData ( stream , session , callback ) {
async function process () {
const chunks : Uint8Array [] = [];
for await ( const chunk of stream ) {
chunks . push ( chunk );
}
const content = Buffer . concat ( chunks ). toString ();
// Reject spam
if ( content . includes ( "SPAM" )) {
const err = new Error ( "Spam detected" ) as any ;
err . responseCode = 550 ;
return callback ( err );
}
callback ( null );
}
process (). catch ( callback );
}
Always consume the entire stream even if you plan to reject the message. Otherwise, the connection may hang.
Size limits
Set a maximum message size:
const server = new SMTPServer ({
authOptional: true ,
size: 10 * 1024 * 1024 , // 10 MB limit
onData ( stream , session , callback ) {
async function process () {
for await ( const chunk of stream ) {
// drain
}
if ( stream . sizeExceeded ) {
const err = new Error ( "Message too large" ) as any ;
err . responseCode = 552 ;
return callback ( err );
}
console . log ( `Received ${ stream . byteLength } bytes` );
callback ( null );
}
process (). catch ( callback );
},
});
Next steps
Add authentication Require users to authenticate before sending
Enable TLS Encrypt connections with STARTTLS
Configuration reference Explore all server options
Callbacks reference Learn about all lifecycle callbacks