Trapbox is an ultra-lightweight, self-hosted email testing server offering a modern Webmail interface and comprehensive APIs, while ensuring strict isolation of inboxes (multi-tenant). Email routing will be handled dynamically based on the SMTP authentication credentials used by the sending application.
  • TypeScript 86.8%
  • Vue 12.5%
  • JavaScript 0.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Sébastien Denooz 6fe98ed102
All checks were successful
CI / quality (pull_request) Successful in 1m41s
CI / docker (pull_request) Successful in 1m17s
CI / e2e (chromium) (pull_request) Successful in 2m15s
CI / e2e (firefox) (pull_request) Successful in 3m15s
CI / e2e (webkit) (pull_request) Successful in 4m17s
CI / quality (push) Successful in 1m41s
CI / docker (push) Successful in 30s
CI / e2e (chromium) (push) Successful in 2m11s
CI / e2e (firefox) (push) Successful in 3m3s
CI / e2e (webkit) (push) Successful in 4m9s
Release / quality (push) Successful in 1m35s
Release / docker (push) Successful in 30s
Release / e2e (chromium) (push) Successful in 2m10s
Release / e2e (firefox) (push) Successful in 3m6s
Release / e2e (webkit) (push) Successful in 4m7s
Release / test (push) Successful in 0s
Release / release (push) Successful in 2m36s
chore(release): include the kept sign-in in the 1.1.0 changelog
2026-10-01 02:28:49 +02:00
.forgejo/workflows ci(release): install envsubst for cosign-installer in the Forgejo release job 2026-09-28 10:55:15 +02:00
.github/workflows ci(release): use action refs that exist and replace the actions Forgejo cannot run 2026-09-28 02:58:53 +02:00
app fix(connection): give Laravel MAIL_SCHEME instead of MAIL_ENCRYPTION 2026-10-01 02:10:19 +02:00
deploy docs: document using, administering, configuring, deploying and operating Trapbox 2026-10-01 02:10:41 +02:00
docker refactor(deploy): move the production Compose file next to the Docker Compose deployment 2026-09-29 10:05:38 +02:00
docs fix(local-mode): keep sign-in on a data directory created with it 2026-10-01 02:28:49 +02:00
i18n/locales feat(local-mode): run as is, without sign-in or SMTP authentication, when no public URL is set 2026-09-28 14:29:27 +02:00
public feat(app): build the webmail shell with the environment selector and localized interface 2026-09-24 23:00:35 +02:00
scripts ci(release): publish signed multi-architecture images from version tags 2026-09-25 16:02:25 +02:00
server fix(local-mode): keep sign-in on a data directory created with it 2026-10-01 02:28:49 +02:00
shared feat(local-mode): run as is, without sign-in or SMTP authentication, when no public URL is set 2026-09-28 14:29:27 +02:00
tasks fix(local-mode): keep sign-in on a data directory created with it 2026-10-01 02:28:49 +02:00
test fix(local-mode): keep sign-in on a data directory created with it 2026-10-01 02:28:49 +02:00
.dockerignore build(docker): add the production container image and its test suite 2026-09-25 15:43:58 +02:00
.editorconfig build: scaffold Nuxt 4 application with i18n, lint, typecheck and Vitest 2026-09-24 15:32:27 +02:00
.env.example feat(local-mode): run as is, without sign-in or SMTP authentication, when no public URL is set 2026-09-28 14:29:27 +02:00
.gitignore build(docker): add the production container image and its test suite 2026-09-25 15:43:58 +02:00
.gitlab-ci.yml ci: run the image tests on the runner's Docker engine with a full client 2026-09-26 20:13:32 +02:00
.nuxtrc build: scaffold Nuxt 4 application with i18n, lint, typecheck and Vitest 2026-09-24 15:32:27 +02:00
.nvmrc build: scaffold Nuxt 4 application with i18n, lint, typecheck and Vitest 2026-09-24 15:32:27 +02:00
CHANGELOG.md chore(release): include the kept sign-in in the 1.1.0 changelog 2026-10-01 02:28:49 +02:00
cliff.toml ci(release): publish signed multi-architecture images from version tags 2026-09-25 16:02:25 +02:00
eslint.config.mjs feat(core): add the outbound request policy and its safe HTTP client 2026-09-25 06:08:19 +02:00
LICENSE build: scaffold Nuxt 4 application with i18n, lint, typecheck and Vitest 2026-09-24 15:32:27 +02:00
nuxt.config.ts test(perf): add the performance validation suite of the §15 targets 2026-09-25 20:32:28 +02:00
package-lock.json chore(release): 1.1.0 2026-10-01 02:10:58 +02:00
package.json chore(release): 1.1.0 2026-10-01 02:10:58 +02:00
playwright.config.ts test(e2e): keep parallel tests apart from instance-wide changes and sign-in load 2026-09-27 19:56:37 +00:00
README.md fix(local-mode): keep sign-in on a data directory created with it 2026-10-01 02:28:49 +02:00
renovate.json ci(release): publish signed multi-architecture images from version tags 2026-09-25 16:02:25 +02:00
THIRD_PARTY_NOTICES.md feat(analysis): check the HTML compatibility of a message 2026-09-25 06:49:45 +02:00
tsconfig.json build: scaffold Nuxt 4 application with i18n, lint, typecheck and Vitest 2026-09-24 15:32:27 +02:00
vitest.config.ts test(perf): add the performance validation suite of the §15 targets 2026-09-25 20:32:28 +02:00

Trapbox

Self-hosted, multi-tenant email testing. Point your applications at Trapbox instead of a real mail server: every email is captured, nothing is ever delivered, and each team, client or project only sees its own inboxes.

License: MIT Docker image: alchemicstudiohq/trapbox

Read the documentation: getting started, user and administration guides with screenshots, sending examples for every common framework, the full configuration reference, and deployment guides for Docker, Kubernetes and Linux servers with nginx or Apache.

A message in Trapbox

Why Trapbox?

Mail catchers such as Mailpit are perfect for one developer or one team, but every message lands in a single shared inbox. Trapbox routes each message according to the SMTP credentials the sending application used. One instance can therefore serve many applications, stages and clients, with strict isolation, per-inbox quotas, roles and your own branding.

How it works

flowchart LR
    A["Staging app"] -->|"SMTP · credential A"| T["Trapbox"]
    B["CI pipeline"] -->|"SMTP · credential B"| T
    T --> EA["Environment A"]
    T --> EB["Environment B"]
    EA --> O["Webmail · API · webhooks · RSS"]
    EB --> O
  1. Create an environment (an isolated inbox) and generate an SMTP credential for it.
  2. Configure your application with that credential. Trapbox accepts any recipient address and routes by credential, so two applications can send to the same test address without their messages ever mixing.
  3. Inspect the messages in the webmail, or assert on them through the API from your tests.

Trapbox never relays: no message ever leaves it.

Features (v1)

Capture

  • Mandatory SMTP authentication. Each credential belongs to one environment, and an environment can have several credentials for rotation without downtime.
  • The exact raw message, the envelope (including Bcc recipients) and the session details are kept for every message.
  • TLS modes: none, STARTTLS or implicit TLS with your own certificate; PROXY protocol support.
  • Size limits, rate limits, quotas with rotation or rejection, and a disk guard, each with a documented SMTP response.

Inspect

  • Real-time webmail with search, filters, tags and pinned messages.
  • HTML rendering in an isolated frame (remote content blocked by default), text version, HTML source, raw message, headers, envelope and session, attachments, .eml download.
  • Link checker, responsive preview, HTML compatibility check across email clients, spam score (SpamAssassin).

Automate

  • REST API with an OpenAPI reference, a wait-for-message endpoint for end-to-end tests, and a provisioning API (for example one inbox per merge request).
  • Mailpit-compatible API: existing Mailpit clients only need a new base URL and credentials.
  • Webhooks (JSON, Slack, Discord, Microsoft Teams), personal RSS feeds, and failure injection to simulate SMTP errors and slow servers.

Share safely

  • Projects and environments with Viewer, Member and Manager roles, plus an audit log.
  • Per-environment retention, storage and message quotas, and expiry dates for temporary environments.
  • Whitelabel (name, logo, favicon, accent color), OpenID Connect single sign-on, interface in English, French and Dutch.

Operate

  • One container, one volume, SQLite; images for linux/amd64 and linux/arm64.
  • Health and readiness endpoints, Prometheus metrics, JSON logs, optional Sentry-compatible error reporting.
  • Automatic migrations with pre-upgrade snapshots, plus backup and restore commands.

Quick start

On your machine: nothing to configure

docker run -d --name trapbox -p 127.0.0.1:1125:1125 -p 127.0.0.1:3025:3000 alchemicstudiohq/trapbox

Open http://localhost:3025: there is no sign-in. Point your application at Trapbox, with no credentials (or any):

MAIL_MAILER=smtp
MAIL_HOST=127.0.0.1
MAIL_PORT=1125
MAIL_USERNAME=null
MAIL_PASSWORD=null

Messages land in the Local environment. The API (/api/v1, reference at /api/v1/docs) and the Mailpit-compatible API (http://localhost:3025/mailpit) need no token either. Add -v trapbox-data:/data to keep the messages when the container is recreated.

This is local mode: it applies as long as TRAPBOX_PUBLIC_URL is not set, for requests addressed to localhost, an IP address or a single-label name such as a Docker Compose service. A data directory created with sign-in (by Trapbox 1.0, or with a public URL) keeps sign-in: upgrading never opens an instance. Keep the ports published on 127.0.0.1, as above: anyone who can reach them is the administrator. Every feature is there, including projects, environments with their own SMTP credentials, tokens and webhooks.

For a team: a shared instance

docker run -d --name trapbox \
  -p 3000:3000 -p 1125:1125 \
  -v trapbox-data:/data \
  -e TRAPBOX_PUBLIC_URL=https://trapbox.example.com \
  alchemicstudiohq/trapbox

# The first administrator's password is printed once in the logs
docker logs trapbox

With a public URL, every access is authenticated: sign in as admin@trapbox.local with the printed password (you will be asked to change it), create an environment and generate an SMTP credential. Then point your application at Trapbox:

Setting Value
Host Your Trapbox host
Port 1125
Encryption None (default TLS mode)
Authentication PLAIN or LOGIN
Username and password The environment's SMTP credential

Each environment's connection panel provides ready-to-copy settings for Laravel, Symfony and Node.js (Nodemailer); sending email covers many more frameworks.

Configuration

Trapbox is configured through environment variables, read at start-up. The essentials:

Variable Default Purpose
TRAPBOX_PUBLIC_URL the address of each request Public address of the web interface: set it for any instance others use (it turns authentication on)
TRAPBOX_AUTH none without a public URL, required with one none: local mode, no sign-in and no SMTP authentication; required: every access is authenticated
TRAPBOX_LOCAL_HOSTS — Extra host names local mode accepts, besides localhost, IP addresses and single-label names
TRAPBOX_DATA_DIR trapbox in the system's temporary directory (/data in the image) Database, messages, uploads and snapshots: set it to keep them
TRAPBOX_SMTP_PORT 1125 SMTP port
TRAPBOX_SMTP_TLS_MODE none none, starttls or implicit (the last two need a certificate)
TRAPBOX_ADMIN_EMAIL admin@trapbox.local First administrator
TRAPBOX_ADMIN_PASSWORD random, printed once First administrator's password
TRAPBOX_SETUP_MODE auto web to create the first administrator through a setup wizard instead

Secrets can also be provided as files, using the same variable name with a _FILE suffix (Docker and Kubernetes secrets). Every variable, with its format, validation rules and examples, is in the configuration reference.

Deployment notes

Step-by-step guides and manifests for Docker, Docker Compose, Coolify and Traefik, Kubernetes and OpenShift, a Linux server with nginx or Apache, and any reverse proxy are in docs/deploy. For Coolify and similar platforms, deploy/docker-compose/docker-compose.prod.yml is ready to deploy: sign-in required, every variable listed with its default. In short:

  • Trapbox runs as a single instance: update it by stopping the old container before starting the new one, never with rolling updates.
  • The data volume must be local block storage (not NFS or SMB).
  • SMTP cannot go through an HTTP router: publish its port, or use a TCP entrypoint (Traefik, Coolify) or a LoadBalancer or NodePort service (Kubernetes, OpenShift) with the PROXY protocol so that client IP addresses stay accurate.
  • The image runs as a non-root user, accepts arbitrary user IDs (OpenShift) and works with a read-only root filesystem.

Backup and restore

trapbox backup <file> writes a consistent archive of the database, messages, uploads and generated secret key while Trapbox runs; trapbox restore <file> puts it back with Trapbox stopped. Back up before every upgrade, test restores regularly, and keep a configured TRAPBOX_SECRET_KEY with your backups. The procedure is in docs/operations/backup-restore.md.

What Trapbox is not

  • A mail server that delivers. Trapbox never relays, forwards or releases email.
  • A high-availability cluster. It runs as a single instance by design.
  • A mail client. No composing, replying, POP3 or IMAP.

Tech stack

Nuxt 4 (server-side rendering) and Nuxt UI 4 · Node.js 24 LTS · smtp-server (Nodemailer) · PostalMime · SQLite

Development

Requirements: Node.js 24 or later and npm.

npm install          # install dependencies (runs `nuxt prepare`)
npm run dev          # development server on http://localhost:3000

Without configuration, Trapbox keeps its data in the system's temporary directory (/tmp/trapbox on Linux), which a reboot may empty; cp .env.example .env keeps it in the project instead. The first start creates the administrator admin@trapbox.local and prints its password once in the terminal; the sign-in page reminds you until it is changed.

End-to-end tests run against the production build with Playwright:

npx playwright install --with-deps chromium   # once: Playwright's Chromium and its system libraries
npm run build
npm run test:e2e                              # Chromium UI tests and HTTP API tests

Locally, when /usr/bin/chromium exists, Playwright uses it instead of its own Chromium (useful on distributions that --with-deps does not support); set PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH to use another browser binary. CI runs the UI tests on Chromium, Firefox and WebKit; npm run test:e2e:all does the same locally (npx playwright install firefox webkit first). The UI tests include the accessibility audits and keyboard tests described in docs/accessibility.md.

Script Purpose
npm run dev Development server with hot reload
npm run build Production build (.output/)
npm run start Run the production build
npm run lint / npm run lint:fix ESLint (with stylistic rules)
npm run typecheck Type check with nuxt typecheck
npm run test:unit Unit tests and Nuxt-environment tests (Vitest)
npm run test:integration Integration tests (Vitest)
npm run test:e2e End-to-end tests (Playwright)
npm run test:e2e:all End-to-end tests on Chromium, Firefox and WebKit
npm run test:perf Performance targets of §15 on the production build (PERF_SCALE=full before each minor release: docs/operations/performance.md)
npm run test:docker Builds the production image and checks it (Docker required; a remote engine is found from DOCKER_HOST, or named by TRAPBOX_DOCKER_HOST)
npm run licenses:generate Third-party license inventory (docs/third-party-licenses.txt, generated in the image build)

The production image is built from docker/Dockerfile; docker compose -f docker/docker-compose.yml up --build runs it locally, in local mode, read-only, with a named volume.

Development guidelines are in docs/guidelines.md. Releases follow docs/releasing.md: a vX.Y.Z tag builds, scans, publishes and signs the image, and the changelog (npm run changelog) comes from the Conventional Commits.

Contributing

Issues, questions and ideas are welcome.

  • Commits follow Conventional Commits; the changelog is generated from them.
  • Every change comes with tests: unit, integration and end-to-end (Playwright).

Security

Please report vulnerabilities privately (for example through a confidential issue), never in a public issue.

Acknowledgements

License

Trapbox is developed by Alchemic Studio and released under the MIT license.