Open-source local email sandbox: SMTP + web UI + API in one binary. Catch every email you send.
6.2K
GitHub · Docs · API · Mailtrap.io
Mailtrap Local is an open-source SMTP server you run on your own machine. It catches every email your app sends, stores them in a local SQLite database, and gives you a web sandbox to browse, search, and inspect them in. Nothing leaves your machine unless you tell it to.
One self-contained binary in a distroless image — under 7 MB, linux/amd64 + linux/arm64, MIT-licensed.

SMTP server on 3535 — point any app, framework, or test suite at it. Anonymous SMTP — no authentication, no TLS, nothing to configure but the host and port.
Web sandbox on 3550 — search caught mail and inspect rendered HTML, plain text, raw source, headers, and attachments.
REST API on 3550 — documented with an OpenAPI 3.1 spec, served at /api/v1/openapi.yaml.
HTML check — score an email's compatibility across major mail clients, weighted by real client market share.
Send to Cloud — promote any caught message to your hosted Mailtrap sandbox in one click, so a designer or PM can review what your local code produced without shell access.
Relay and auto-relay — release a caught message through any upstream SMTP server (Mailtrap, SendGrid, Mailgun, SES, Postmark) when you need it to land in a real inbox.
Outbound webhooks — HMAC-signed POST on every caught message, so your app can react in tests.
Categories — label mail at send time with X-MT-Category (the same header convention as Mailtrap cloud) and filter on it in the UI and API.
Live updates — new mail appears over a WebSocket, no refresh.
Automatic pruning — a configurable retention cap evicts the oldest messages.

docker run -d \
--name mailtrap-local \
--restart unless-stopped \
-p 3535:3535 \
-p 3550:3550 \
-v mailtrap-local:/var/lib/mailtrap-local \
mailtrap/mailtrap-local:latest
Open http://127.0.0.1:3550 for the sandbox, and point your app's SMTP client at 127.0.0.1:3535.
| Port | Purpose |
|---|---|
3535 | SMTP ingest |
3550 | Web sandbox + REST API + WebSocket |
services:
mailtrap-local:
image: mailtrap/mailtrap-local:latest
restart: unless-stopped
ports:
- "3535:3535"
- "3550:3550"
volumes:
- mailtrap-local:/var/lib/mailtrap-local
volumes:
mailtrap-local:
Other services on the same Compose network reach the SMTP server at mailtrap-local:3535.
The image declares a volume at /var/lib/mailtrap-local. It holds two files that belong together:
db.sqlite3 — every caught messagesecret.key — encrypts the relay, cloud, and webhook credentials stored in that databaseMount the whole directory, not just the database. The image pins MAILTRAP_LOCAL_SECRET_KEY_FILE onto the volume for exactly this reason: if the key lived on the container filesystem, a docker pull upgrade would generate a fresh one and silently break every saved connection.
The image's default command already sets the flags a container needs:
| Flag | Value in the image |
|---|---|
--http-listen | 0.0.0.0:3550 |
--smtp-listen | 0.0.0.0:3535 |
--db | /var/lib/mailtrap-local/db.sqlite3 |
--unsafe-non-loopback | set |
If you override the command, keep --unsafe-non-loopback and repeat the other three — Docker replaces the default command wholesale rather than merging with it. The binary refuses to bind a non-loopback address without --unsafe-non-loopback, and inside a container it has to bind 0.0.0.0 to be reachable through -p. Host exposure is still governed by your port mapping, not by the bind.
You can set these in the UI, or pin them from a YAML file mounted into the container:
docker run -d \
-p 3535:3535 -p 3550:3550 \
-v mailtrap-local:/var/lib/mailtrap-local \
-v "$PWD/config.yml:/etc/mailtrap-local/config.yml:ro" \
-e MAILTRAP_LOCAL_CONFIG=/etc/mailtrap-local/config.yml \
-e MAILTRAP_API_TOKEN=... \
mailtrap/mailtrap-local:latest
storage:
max_messages: 500
cloud:
api_token: ${MAILTRAP_API_TOKEN}
sandbox_id: 1234567
relay:
host: smtp.example.com # your upstream SMTP host
port: 587
username: ${SMTP_USERNAME}
password: ${SMTP_PASSWORD}
auto_relay_enabled: false
webhook:
url: http://host.docker.internal:4000/hooks/mail
secret: ${WEBHOOK_SECRET}
enabled: true
Values support ${ENV_VAR} interpolation. Any key present in the file overrides the equivalent value in the database and becomes read-only in the UI — which is what you want for a container you rebuild often.
| Tag | Use it for |
|---|---|
latest | the most recent release |
0.4.1 | pinning a release — recommended in CI |
0.4.1-amd64, 0.4.1-arm64 | single-arch pulls; rarely needed, since latest and the version tags are manifest lists |
The same images are mirrored to ghcr.io/mailtrap/mailtrap-local.
Mailtrap Local is a tool for one developer on one machine. There are no user accounts, no authentication, no multi-tenancy, no auth on the local API, and no RBAC. Storage is SQLite only.
If you need shared inboxes, team access, and a hosted setup, use Mailtrap — the hosted product this complements. Any message you catch locally can be pushed there with Send to Cloud.
Homebrew, Snap, Nix, and prebuilt binaries for macOS and Linux — see the README.
MIT — source on GitHub. Issues and pull requests welcome.
Content type
Image
Digest
sha256:07467e8b6…
Size
6.8 MB
Last updated
about 2 months ago
docker pull mailtrap/mailtrap-local