Node.js · Self-hosted
Catch mail, bounce it properly, keep a copy.
Bounce & Forward listens on port 25 for addresses you choose, captures the full message, then rejects it with a real SMTP error — so the sender's own mail server generates a genuine bounce, while you keep a browsable copy in a password-protected web UI. No relay, no outbound mail, ever.
How it works
[Sender's mail server] ──── SMTP, port 25 ────► [Bounce & Forward]
│ captures the full message
│ during MAIL FROM / RCPT TO / DATA
│ then rejects with 5xx, not 250 OK
▼
[SQLite storage]
│ browsable via
▼
[Password-protected web UI]
The sender's own mail server is the one that generates the bounce back to them — this daemon never originates outbound mail, and never relays anything anywhere. Rejecting inline, during the SMTP conversation itself, avoids "backscatter": a forged sender address never gets a bounce mailed to it after the fact.
What it does
Real bounces, no backscatter
Rejects inline during the SMTP conversation, before ever responding — the sender's own mail server generates the bounce, so a forged sender is never mailed a bounce it didn't ask for.
Works where outbound 25 is blocked
Never opens an outbound SMTP connection, so it runs fine on hosts like EC2 that block or throttle outbound port 25 by default.
Live-managed recipients
Accepted addresses and whole domains are managed at /recipients in the web UI — no restart or redeploy, changes apply to the very next connection.
Quota and retention
A configurable storage quota (default 5GB) and retention window (default 365 days), enforced after every message plus an hourly sweep.
Password-protected web UI
Single-user login, bcrypt-hashed and rate-limited, CSRF-protected admin actions, and HTML bodies rendered in a sandboxed iframe with scripts disabled.
One-command install
A single interactive install.sh provisions Node, the service user, the systemd unit, and optional Caddy TLS — or run it non-interactively with env vars.
Get started
- Launch an Ubuntu EC2 instance with an Elastic IP — PaaS platforms like Railway or Render don't expose raw port 25 to the internet.
- Open inbound TCP 25 in the security group (plus 80/443 if using Caddy for TLS on the web UI).
- SSH in and run
install.shas root — interactive by default, or fully non-interactive via environment variables for CI. - Manage accepted recipients and storage quota/retention live at
/recipientsand/settings— no redeploy needed.
Full setup, TLS configuration, and security notes live in the project README. github.com/gareth-c/bounce-and-forward →