- TypeScript 86.8%
- Vue 12.5%
- JavaScript 0.5%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
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
|
||
| .forgejo/workflows | ||
| .github/workflows | ||
| app | ||
| deploy | ||
| docker | ||
| docs | ||
| i18n/locales | ||
| public | ||
| scripts | ||
| server | ||
| shared | ||
| tasks | ||
| test | ||
| .dockerignore | ||
| .editorconfig | ||
| .env.example | ||
| .gitignore | ||
| .gitlab-ci.yml | ||
| .nuxtrc | ||
| .nvmrc | ||
| CHANGELOG.md | ||
| cliff.toml | ||
| eslint.config.mjs | ||
| LICENSE | ||
| nuxt.config.ts | ||
| package-lock.json | ||
| package.json | ||
| playwright.config.ts | ||
| README.md | ||
| renovate.json | ||
| THIRD_PARTY_NOTICES.md | ||
| tsconfig.json | ||
| vitest.config.ts | ||
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.
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.
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
- Create an environment (an isolated inbox) and generate an SMTP credential for it.
- 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.
- 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,
.emldownload. - 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/amd64andlinux/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
- Mailpit, for the inspiration and the API Trapbox is compatible with.
- Can I email, whose data (MIT license) powers the HTML compatibility check.
- Nuxt, Nuxt UI, smtp-server by Nodemailer, and PostalMime.
License
Trapbox is developed by Alchemic Studio and released under the MIT license.
