fumi

Getting Started

Install fumi and send your first SMTP session.

Install

bun add @puiusabin/fumi

Create a server

server.ts
import { Fumi } from "@puiusabin/fumi";

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

app.onData(async (ctx, next) => {
  const chunks: Uint8Array[] = [];
  for await (const chunk of ctx.stream) {
    chunks.push(chunk);
  }
  const raw = Buffer.concat(chunks).toString();
  console.log(raw);
  await next();
});

await app.listen(2525);
console.log("SMTP server listening on port 2525");

Test it

Send a message with netcat:

printf "EHLO test\r\nMAIL FROM:<sender@example.com>\r\nRCPT TO:<you@example.com>\r\nDATA\r\nSubject: test\r\n\r\nHello\r\n.\r\nQUIT\r\n" | nc localhost 2525

Next steps

Rejecting connections

ctx.reject() throws an SMTPError internally and has return type never. The TypeScript compiler treats any code after it as unreachable.

server.ts
app.onMailFrom(async (ctx, next) => {
  if (ctx.address.address.endsWith("@blocked.example")) {
    ctx.reject("Domain blocked", 550);
    // unreachable — compiler knows this
  }
  await next();
});

Closing the server

app.listen() returns a Promise<void>. app.close() does too.

server.ts
const app = new Fumi({ authOptional: true });
await app.listen(2525);

// later
await app.close();

On this page