Complete reference for all TypeScript types, interfaces, and classes exported by Fumi.
SMTPError
Custom error class for SMTP protocol errors. Extends the standard JavaScript Error class with an SMTP response code.
class SMTPError extends Error {
responseCode : number ;
constructor ( message : string , responseCode ?: number );
}
Human-readable error message.
SMTP response code (e.g., 550, 535, 552). Defaults to 550 if not specified.
Example
import { SMTPError } from "@puiusabin/fumi" ;
// Throw with default code (550)
throw new SMTPError ( "Mailbox unavailable" );
// Throw with custom code
throw new SMTPError ( "Authentication failed" , 535 );
throw new SMTPError ( "Message too large" , 552 );
Core Interfaces
Address
Represents an email address with optional SMTP parameters.
interface Address {
address : string ;
args : Record < string , unknown >;
}
args
Record<string, unknown>
required
SMTP parameters passed with the address (e.g., SIZE, BODY).
Envelope
Contains the sender and recipient information for an email transaction.
interface Envelope {
mailFrom : Address ;
rcptTo : Address [];
}
The sender address from the MAIL FROM command.
Array of recipient addresses from RCPT TO commands.
Session
Represents an active SMTP session with connection and transaction metadata.
interface Session {
id : string ;
secure : boolean ;
remoteAddress : string ;
clientHostname : string ;
openingCommand : string ;
user : unknown ;
envelope : Envelope ;
transmissionType : string ;
}
Unique identifier for this SMTP session.
Whether the connection is using TLS/SSL.
IP address of the connected client.
Hostname provided by the client in EHLO/HELO command.
The command used to initiate the session (“EHLO” or “HELO”).
User data set during authentication via ctx.accept(user).
The email envelope containing sender and recipients.
Type of transmission (e.g., “ESMTP”, “SMTP”).
Credentials
Authentication credentials provided by the client.
interface Credentials {
method : string ;
username : string ;
password : string ;
validatePassword : ( password : string ) => boolean ;
}
Authentication method used (e.g., “PLAIN”, “LOGIN”).
Username provided by the client.
Password provided by the client (plain text after decoding).
validatePassword
(password: string) => boolean
required
Helper function to validate a password hash. Returns true if the password matches.
Context Types
Context objects are passed to middleware functions for each SMTP phase.
ConnectContext
Context for the connection phase.
interface ConnectContext {
session : Session ;
reject ( message ?: string , code ?: number ) : never ;
}
The current SMTP session.
reject
(message?: string, code?: number) => never
required
Rejects the connection and closes it. Default code is 550.
Example
app . onConnect ( async ( ctx , next ) => {
if ( ctx . session . remoteAddress === "192.168.1.100" ) {
ctx . reject ( "Access denied" , 550 );
}
await next ();
});
AuthContext
Context for the authentication phase.
interface AuthContext {
session : Session ;
credentials : Credentials ;
accept ( user : unknown ) : void ;
reject ( message ?: string , code ?: number ) : never ;
}
The current SMTP session.
Authentication credentials from the client.
accept
(user: unknown) => void
required
Accepts the authentication and stores user data in session.user.
reject
(message?: string, code?: number) => never
required
Rejects the authentication. Default code is 535.
Example
app . onAuth ( async ( ctx , next ) => {
const user = await db . findUser ( ctx . credentials . username );
if ( user && ctx . credentials . validatePassword ( user . passwordHash )) {
ctx . accept ({ id: user . id , email: user . email });
} else {
ctx . reject ( "Invalid username or password" , 535 );
}
await next ();
});
MailFromContext
Context for the MAIL FROM phase.
interface MailFromContext {
session : Session ;
address : Address ;
reject ( message ?: string , code ?: number ) : never ;
}
The current SMTP session.
The sender address from the MAIL FROM command.
reject
(message?: string, code?: number) => never
required
Rejects the sender address. Default code is 550.
Example
app . onMailFrom ( async ( ctx , next ) => {
const blockedDomains = [ "spam.com" , "abuse.net" ];
const domain = ctx . address . address . split ( "@" )[ 1 ];
if ( blockedDomains . includes ( domain )) {
ctx . reject ( "Sender domain blocked" , 550 );
}
await next ();
});
RcptToContext
Context for the RCPT TO phase. Called once for each recipient.
interface RcptToContext {
session : Session ;
address : Address ;
reject ( message ?: string , code ?: number ) : never ;
}
The current SMTP session.
The recipient address from the RCPT TO command.
reject
(message?: string, code?: number) => never
required
Rejects this specific recipient. Default code is 550.
Example
app . onRcptTo ( async ( ctx , next ) => {
const recipient = ctx . address . address ;
const exists = await db . mailboxExists ( recipient );
if ( ! exists ) {
ctx . reject ( "Mailbox not found" , 550 );
}
await next ();
});
DataContext
Context for the DATA phase.
interface DataContext {
session : Session ;
stream : ReadableStream < Uint8Array >;
sizeExceeded : boolean ;
reject ( message ?: string , code ?: number ) : never ;
}
The current SMTP session.
stream
ReadableStream<Uint8Array>
required
Readable stream of the message data. Must be consumed by piping to a writable stream.
Whether the message exceeded the size limit set in FumiOptions.size.
reject
(message?: string, code?: number) => never
required
Rejects the message data. Default code is 552.
Example
app . onData ( async ( ctx , next ) => {
await next ();
// Save to file
const file = Bun . file ( `./messages/ ${ ctx . session . id } .eml` );
await ctx . stream . pipeTo ( file . writer ());
if ( ctx . sizeExceeded ) {
ctx . reject ( "Message exceeds maximum size" , 552 );
}
});
CloseContext
Context for the close phase.
interface CloseContext {
session : Session ;
}
The SMTP session that is closing.
Example
app . onClose ( async ( ctx ) => {
console . log ( `Connection closed: ${ ctx . session . id } ` );
await cleanupSession ( ctx . session . id );
});
Function Types
Middleware
Middleware function type for SMTP phase handlers.
type Middleware < T > = (
ctx : T ,
next : () => Promise < void >
) => Promise < void >;
Context object for the current phase (ConnectContext, AuthContext, etc.).
next
() => Promise<void>
required
Function to call the next middleware in the chain. Must be awaited.
Returns: A Promise that resolves when the middleware completes.
Example
const myMiddleware : Middleware < ConnectContext > = async ( ctx , next ) => {
console . log ( "Before next middleware" );
await next ();
console . log ( "After next middleware" );
};
app . onConnect ( myMiddleware );
Plugin
Plugin function type for registering reusable middleware bundles.
type Plugin = ( app : Fumi ) => void ;
The Fumi instance to register middleware on.
Example
import type { Plugin } from "@puiusabin/fumi" ;
function myPlugin ( options : { prefix : string }) : Plugin {
return ( app ) => {
app . onConnect ( async ( ctx , next ) => {
console . log ( ` ${ options . prefix } : Client connected` );
await next ();
});
};
}
// Usage
app . use ( myPlugin ({ prefix: "[SMTP]" }));
Configuration
FumiOptions
Configuration options for the Fumi SMTP server.
interface FumiOptions {
// TLS
secure ?: boolean ;
key ?: string | Buffer ;
cert ?: string | Buffer ;
ca ?: string | Buffer ;
requireTLS ?: boolean ;
// Auth
authMethods ?: string [];
authOptional ?: boolean ;
// Limits
size ?: number ;
maxClients ?: number ;
// Behavior
banner ?: string ;
disabledCommands ?: string [];
hideSTARTTLS ?: boolean ;
hidePIPELINING ?: boolean ;
hideENHANCEDSTATUSCODES ?: boolean ;
hideDSN ?: boolean ;
hideREQUIRETLS ?: boolean ;
lmtp ?: boolean ;
useXClient ?: boolean ;
useXForward ?: boolean ;
allowInsecureAuth ?: boolean ;
closeTimeout ?: number ;
}
Start server in TLS mode (implicit TLS on port 465).
TLS private key (PEM format).
TLS certificate (PEM format).
TLS certificate authority (PEM format).
Require clients to use STARTTLS before authentication.
Show Authentication Options
Allowed authentication methods (e.g., [“PLAIN”, “LOGIN”, “CRAM-MD5”]).
Allow unauthenticated clients to send mail.
Allow authentication over non-TLS connections.
Maximum message size in bytes. Required for size validation.
Maximum number of concurrent client connections.
Custom SMTP banner message shown to clients.
List of SMTP commands to disable (e.g., [“AUTH”, “STARTTLS”]).
Hide STARTTLS from capability list.
Hide PIPELINING from capability list.
Hide ENHANCEDSTATUSCODES from capability list.
Hide DSN (Delivery Status Notification) from capability list.
Hide REQUIRETLS from capability list.
Use LMTP (Local Mail Transfer Protocol) instead of SMTP.
Enable XCLIENT command for proxy protocol.
Enable XFORWARD command for proxy protocol.
Timeout in milliseconds for closing idle connections.
Example
import { Fumi } from "@puiusabin/fumi" ;
const app = new Fumi ({
// TLS configuration
secure: false ,
requireTLS: true ,
key: await Bun . file ( "./server-key.pem" ). text (),
cert: await Bun . file ( "./server-cert.pem" ). text (),
// Authentication
authMethods: [ "PLAIN" , "LOGIN" ],
authOptional: false ,
// Limits
size: 25 * 1024 * 1024 , // 25MB
maxClients: 100 ,
// Customization
banner: "Welcome to MyMail SMTP Server" ,
closeTimeout: 30000 ,
});