Skip to main content

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.

Lifecycle callbacks hook into each phase of an SMTP session. Set them as constructor options or override them on the server instance after construction. Call callback(null) to accept or callback(error) to reject. To send a custom SMTP error code, set error.responseCode:
const err = new Error("Mailbox does not exist");
err.responseCode = 550;
callback(err);

onConnect

Called as soon as a client connects, before any SMTP dialogue. Use this to block connections by IP or apply rate limits.
type OnConnectCallback = (
  session: SMTPSession,
  callback: (err?: Error | null) => void
) => void

Parameters

session
SMTPSession
required
The session object for this connection. See Session for details.
callback
(err?: Error | null) => void
required
Call with null to accept the connection, or an Error to reject it.

Example

const server = new SMTPServer({
  onConnect(session, callback) {
    if (session.remoteAddress === "1.2.3.4") {
      const err = new Error("Blocked");
      err.responseCode = 421;
      return callback(err);
    }
    callback(null);
  },
});
Rejecting in onConnect immediately closes the connection without sending a full SMTP greeting.

onSecure

Called after a successful TLS handshake (both implicit TLS and STARTTLS). Use this to inspect client certificates.
type OnSecureCallback = (
  socket: Socket,
  session: SMTPSession,
  callback: (err?: Error | null) => void
) => void

Parameters

socket
Socket
required
The Bun socket object for this connection.
session
SMTPSession
required
The session object. After TLS is established, session.secure is true and session.tlsOptions contains cipher info.
callback
(err?: Error | null) => void
required
Call with null to proceed, or an Error to close the connection.

Example

const server = new SMTPServer({
  requestCert: true,
  onSecure(socket, session, callback) {
    // Inspect TLS details
    console.log("Cipher:", session.tlsOptions);
    // { name: "...", standardName: "...", version: "TLSv1.3" }
    
    callback(null);
  },
});

onAuth

Called when a client sends AUTH. The auth object varies by method — see the Authentication guide for details.
type OnAuthCallback = (
  auth: AuthObject,
  session: SMTPSession,
  callback: (err: Error | null, response?: AuthResponse) => void
) => void

Parameters

auth
AuthObject
required
Authentication credentials. Shape depends on the SASL method used.
session
SMTPSession
required
The session object.
callback
(err: Error | null, response?: AuthResponse) => void
required
Call with (null, response) to accept, or (error) to reject.

AuthResponse

user
unknown
Stored on session.user for the rest of the connection. Use this to track the authenticated user.
message
string
Custom success message returned to the client.
responseCode
number
Custom response code (default is 235).
data
Record<string, string>
XOAUTH2 error challenge data.

Examples

const server = new SMTPServer({
  onAuth(auth, session, callback) {
    if (auth.method === "PLAIN" || auth.method === "LOGIN") {
      if (auth.username === "user" && auth.password === "secret") {
        return callback(null, { user: auth.username });
      }
    }
    callback(new Error("Invalid credentials"));
  },
});

onMailFrom

Called when the client sends MAIL FROM. Use this to validate the sender address or enforce per-user sending policies.
type OnMailFromCallback = (
  address: SMTPAddress,
  session: SMTPSession,
  callback: (err?: Error | null) => void
) => void

Parameters

address
SMTPAddress
required
The sender address. See Session for the structure.Access ESMTP parameters via address.args:
address.args.SIZE     // "1048576"
address.args.BODY     // "8BITMIME"
address.args.SMTPUTF8 // true
session
SMTPSession
required
The session object. session.envelope.mailFrom will be set to this address if you accept.
callback
(err?: Error | null) => void
required
Call with null to accept, or an Error to reject.

Example

const server = new SMTPServer({
  onMailFrom(address, session, callback) {
    // Block specific domains
    if (address.address.endsWith("@blocked.example")) {
      return callback(new Error("Sender not allowed"));
    }
    
    // Enforce authenticated user can only send from their own address
    if (session.user && address.address !== session.user.email) {
      const err = new Error("Not authorized to send from this address");
      err.responseCode = 550;
      return callback(err);
    }
    
    callback(null);
  },
});

onRcptTo

Called once per RCPT TO command. Reject unknown recipients here to avoid accepting mail you cannot deliver.
type OnRcptToCallback = (
  address: SMTPAddress,
  session: SMTPSession,
  callback: (err?: Error | null) => void
) => void

Parameters

address
SMTPAddress
required
The recipient address. Access DSN parameters via address.dsn:
address.dsn?.notify  // ["SUCCESS", "FAILURE"]
address.dsn?.orcpt   // "rfc822;original@example.com"
session
SMTPSession
required
The session object. Accepted recipients are added to session.envelope.rcptTo.
callback
(err?: Error | null) => void
required
Call with null to accept, or an Error to reject.

Example

const server = new SMTPServer({
  onRcptTo(address, session, callback) {
    const known = ["alice@example.com", "bob@example.com"];
    
    if (!known.includes(address.address)) {
      const err = new Error("No such user");
      err.responseCode = 550;
      return callback(err);
    }
    
    callback(null);
  },
});
onRcptTo is called separately for each recipient. A message can have multiple recipients if all are accepted.

onData

Called when the client begins sending the message body. stream is a ReadableStream<Uint8Array>. You must consume it completely before calling callback.
type OnDataCallback = (
  stream: DataStream,
  session: SMTPSession,
  callback: (
    err: Error | null,
    message?: string | Array<string | SMTPError>
  ) => void
) => void

Parameters

stream
DataStream
required
A ReadableStream<Uint8Array> containing the message data. Has two extra properties set after the stream closes:
session
SMTPSession
required
The session object. Access sender and recipients via session.envelope.
callback
(err: Error | null, message?: string | Array<string | SMTPError>) => void
required
Call after fully consuming the stream.
  • First argument: null for success, Error to reject
  • Second argument (optional): Custom success message, or an array of per-recipient responses for LMTP

Examples

const server = new SMTPServer({
  onData(stream, session, callback) {
    const path = `./mail-${Date.now()}.eml`;
    
    stream.pipeTo(Bun.file(path).writer()).then(
      () => {
        console.log(`Saved message to ${path}`);
        callback(null);
      },
      (err) => callback(err)
    );
  },
});
You must fully consume the stream before calling the callback. Failing to do so will cause the connection to hang.

onClose

Called when a connection closes, regardless of reason. No callback — return value is ignored. Use this for cleanup or logging.
type OnCloseCallback = (session: SMTPSession) => void

Parameters

session
SMTPSession
required
The session object for the closed connection.

Example

const server = new SMTPServer({
  onClose(session) {
    console.log(
      `Connection ${session.id} closed after ${session.transaction} transactions`
    );
    
    if (session.error) {
      console.error("Connection error:", session.error);
    }
  },
});
onClose is always called, even if the connection was rejected during onConnect or terminated due to an error.

Build docs developers (and LLMs) love