> ## 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.

# Callbacks

> Lifecycle callback reference for SMTP session handling

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`:

```typescript theme={null}
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.

```typescript theme={null}
type OnConnectCallback = (
  session: SMTPSession,
  callback: (err?: Error | null) => void
) => void
```

### Parameters

<ParamField path="session" type="SMTPSession" required>
  The session object for this connection. See [Session](/api/session) for details.
</ParamField>

<ParamField path="callback" type="(err?: Error | null) => void" required>
  Call with `null` to accept the connection, or an `Error` to reject it.
</ParamField>

### Example

```typescript theme={null}
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);
  },
});
```

<Warning>
  Rejecting in `onConnect` immediately closes the connection without sending a full SMTP greeting.
</Warning>

***

## onSecure

Called after a successful TLS handshake (both implicit TLS and STARTTLS). Use this to inspect client certificates.

```typescript theme={null}
type OnSecureCallback = (
  socket: Socket,
  session: SMTPSession,
  callback: (err?: Error | null) => void
) => void
```

### Parameters

<ParamField path="socket" type="Socket" required>
  The Bun socket object for this connection.
</ParamField>

<ParamField path="session" type="SMTPSession" required>
  The session object. After TLS is established, `session.secure` is `true` and `session.tlsOptions` contains cipher info.
</ParamField>

<ParamField path="callback" type="(err?: Error | null) => void" required>
  Call with `null` to proceed, or an `Error` to close the connection.
</ParamField>

### Example

```typescript theme={null}
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](/guides/authentication) for details.

```typescript theme={null}
type OnAuthCallback = (
  auth: AuthObject,
  session: SMTPSession,
  callback: (err: Error | null, response?: AuthResponse) => void
) => void
```

### Parameters

<ParamField path="auth" type="AuthObject" required>
  Authentication credentials. Shape depends on the SASL method used.

  <Expandable title="AuthObject types">
    **PLAIN / LOGIN:**

    ```typescript theme={null}
    {
      method: "PLAIN" | "LOGIN",
      username: string,
      password: string
    }
    ```

    **CRAM-MD5:**

    ```typescript theme={null}
    {
      method: "CRAM-MD5",
      username: string,
      challenge: string,
      challengeResponse: string,
      validatePassword: (password: string) => boolean
    }
    ```

    **XOAUTH2:**

    ```typescript theme={null}
    {
      method: "XOAUTH2",
      username: string,
      accessToken: string
    }
    ```
  </Expandable>
</ParamField>

<ParamField path="session" type="SMTPSession" required>
  The session object.
</ParamField>

<ParamField path="callback" type="(err: Error | null, response?: AuthResponse) => void" required>
  Call with `(null, response)` to accept, or `(error)` to reject.
</ParamField>

### AuthResponse

<ResponseField name="user" type="unknown" optional>
  Stored on `session.user` for the rest of the connection. Use this to track the authenticated user.
</ResponseField>

<ResponseField name="message" type="string" optional>
  Custom success message returned to the client.
</ResponseField>

<ResponseField name="responseCode" type="number" optional>
  Custom response code (default is `235`).
</ResponseField>

<ResponseField name="data" type="Record<string, string>" optional>
  XOAUTH2 error challenge data.
</ResponseField>

### Examples

<CodeGroup>
  ```typescript PLAIN / LOGIN theme={null}
  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"));
    },
  });
  ```

  ```typescript CRAM-MD5 theme={null}
  const server = new SMTPServer({
    authMethods: ["CRAM-MD5"],
    onAuth(auth, session, callback) {
      if (auth.method === "CRAM-MD5") {
        if (auth.validatePassword("secret")) {
          return callback(null, { user: auth.username });
        }
      }
      callback(new Error("Invalid credentials"));
    },
  });
  ```

  ```typescript Database lookup theme={null}
  const server = new SMTPServer({
    async onAuth(auth, session, callback) {
      try {
        const user = await db.findUser(auth.username);
        if (user && await bcrypt.compare(auth.password, user.passwordHash)) {
          callback(null, { user: user.id });
        } else {
          callback(new Error("Invalid credentials"));
        }
      } catch (err) {
        callback(err);
      }
    },
  });
  ```
</CodeGroup>

***

## onMailFrom

Called when the client sends `MAIL FROM`. Use this to validate the sender address or enforce per-user sending policies.

```typescript theme={null}
type OnMailFromCallback = (
  address: SMTPAddress,
  session: SMTPSession,
  callback: (err?: Error | null) => void
) => void
```

### Parameters

<ParamField path="address" type="SMTPAddress" required>
  The sender address. See [Session](/api/session#smtpaddress) for the structure.

  Access ESMTP parameters via `address.args`:

  ```typescript theme={null}
  address.args.SIZE     // "1048576"
  address.args.BODY     // "8BITMIME"
  address.args.SMTPUTF8 // true
  ```
</ParamField>

<ParamField path="session" type="SMTPSession" required>
  The session object. `session.envelope.mailFrom` will be set to this address if you accept.
</ParamField>

<ParamField path="callback" type="(err?: Error | null) => void" required>
  Call with `null` to accept, or an `Error` to reject.
</ParamField>

### Example

```typescript theme={null}
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.

```typescript theme={null}
type OnRcptToCallback = (
  address: SMTPAddress,
  session: SMTPSession,
  callback: (err?: Error | null) => void
) => void
```

### Parameters

<ParamField path="address" type="SMTPAddress" required>
  The recipient address. Access DSN parameters via `address.dsn`:

  ```typescript theme={null}
  address.dsn?.notify  // ["SUCCESS", "FAILURE"]
  address.dsn?.orcpt   // "rfc822;original@example.com"
  ```
</ParamField>

<ParamField path="session" type="SMTPSession" required>
  The session object. Accepted recipients are added to `session.envelope.rcptTo`.
</ParamField>

<ParamField path="callback" type="(err?: Error | null) => void" required>
  Call with `null` to accept, or an `Error` to reject.
</ParamField>

### Example

```typescript theme={null}
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);
  },
});
```

<Info>
  `onRcptTo` is called separately for each recipient. A message can have multiple recipients if all are accepted.
</Info>

***

## onData

Called when the client begins sending the message body. `stream` is a `ReadableStream<Uint8Array>`. You must consume it completely before calling `callback`.

```typescript theme={null}
type OnDataCallback = (
  stream: DataStream,
  session: SMTPSession,
  callback: (
    err: Error | null,
    message?: string | Array<string | SMTPError>
  ) => void
) => void
```

### Parameters

<ParamField path="stream" type="DataStream" required>
  A `ReadableStream<Uint8Array>` containing the message data. Has two extra properties set after the stream closes:

  <Expandable title="DataStream properties">
    <ResponseField name="byteLength" type="number">
      Total bytes received (available after stream closes)
    </ResponseField>

    <ResponseField name="sizeExceeded" type="boolean">
      `true` if the message exceeded the configured `size` limit
    </ResponseField>
  </Expandable>
</ParamField>

<ParamField path="session" type="SMTPSession" required>
  The session object. Access sender and recipients via `session.envelope`.
</ParamField>

<ParamField path="callback" type="(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
</ParamField>

### Examples

<CodeGroup>
  ```typescript Stream to file theme={null}
  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)
      );
    },
  });
  ```

  ```typescript Collect in memory theme={null}
  const server = new SMTPServer({
    onData(stream, session, callback) {
      async function process() {
        const chunks = [];
        for await (const chunk of stream) {
          chunks.push(chunk);
        }
        const body = Buffer.concat(chunks).toString();
        
        // Parse or store the message
        console.log("From:", session.envelope.mailFrom.address);
        console.log("To:", session.envelope.rcptTo.map(r => r.address));
        console.log("Size:", stream.byteLength);
        
        callback(null);
      }
      process().catch(callback);
    },
  });
  ```

  ```typescript Size limit handling theme={null}
  const server = new SMTPServer({
    size: 10 * 1024 * 1024, // 10 MB
    onData(stream, session, callback) {
      async function process() {
        for await (const chunk of stream) {
          // drain
        }
        
        if (stream.sizeExceeded) {
          const err = new Error("Message too large");
          err.responseCode = 552;
          return callback(err);
        }
        
        callback(null);
      }
      process().catch(callback);
    },
  });
  ```

  ```typescript LMTP per-recipient responses theme={null}
  const server = new SMTPServer({
    lmtp: true,
    onData(stream, session, callback) {
      async function process() {
        for await (const chunk of stream) {}
        
        const responses = session.envelope.rcptTo.map((rcpt) => {
          if (rcpt.address === "bad@example.com") {
            const err = new Error("Mailbox full");
            err.responseCode = 452;
            return err;
          }
          return "Message accepted";
        });
        
        callback(null, responses);
      }
      process().catch(callback);
    },
  });
  ```
</CodeGroup>

<Warning>
  You **must** fully consume the stream before calling the callback. Failing to do so will cause the connection to hang.
</Warning>

***

## onClose

Called when a connection closes, regardless of reason. No callback — return value is ignored. Use this for cleanup or logging.

```typescript theme={null}
type OnCloseCallback = (session: SMTPSession) => void
```

### Parameters

<ParamField path="session" type="SMTPSession" required>
  The session object for the closed connection.
</ParamField>

### Example

```typescript theme={null}
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);
    }
  },
});
```

<Info>
  `onClose` is always called, even if the connection was rejected during `onConnect` or terminated due to an error.
</Info>
