fumi

Introduction

Modern, ultrafast lightweight SMTP Server.

fumi - means letter✉️ in Japanese - is a small, simple, and ultrafast SMTP Server framework built on Bun.

import { Fumi } from "@puiusabin/fumi";

const app = new Fumi({ authOptional: true });

app.onMailFrom(async (ctx, next) => {
  if (ctx.address.address.endsWith("@blocked.example")) {
    ctx.reject("Domain blocked", 550);
  }
  await next();
});

await app.listen(25);

Quick Start

bun add @puiusabin/fumi

Features

  • Ultrafast 🚀 - Runs natively on Bun. No adapter layer, no Node.js compat overhead.
  • Lightweight 🪶 - Tiny core. One runtime dependency (bun-smtp).
  • Middleware 🔗 - koa-style (ctx, next) chains per SMTP phase: connect, auth, mailFrom, rcptTo, data, close.
  • Plugin system 🔌 - A plugin is just (app: Fumi) => void. No registry, no lifecycle hooks.
  • Delightful DX 😃 - Super clean APIs. First-class TypeScript support.

Use Cases

fumi is a minimal SMTP server framework for Bun. It handles the full SMTP lifecycle through composable middleware and runs anywhere Bun runs. Here are some examples of use cases.

  • Inbound email processing
  • Development mail catcher
  • Email gateway
  • Spam filter layer
  • Transactional email relay
  • Custom mail server

Why fumi?

Compared to Haraka

Haraka is the closest prior art — a plugin-based SMTP server for Node.js. fumi differs in a few ways:

  • Built for Bun, not Node.js
  • Middleware instead of hooks/callbacks
  • TypeScript types throughout, no @types/* packages needed
  • ctx.reject() returns never — the compiler enforces control flow

Design

SMTP phases

Each SMTP phase gets its own independent middleware stack:

PhaseMethodContext
ConnectiononConnectConnectContext
AuthenticationonAuthAuthContext
MAIL FROMonMailFromMailFromContext
RCPT TOonRcptToRcptToContext
DATAonDataDataContext
CloseonCloseCloseContext

Independent stacks

Phases are independent — a single global middleware chain would require unsafe union types across all contexts.

On this page