A local email sandbox + catcher for individual developers. Runs on your laptop, catches every email your app sends, and gives you a web sandbox to browse, search, and inspect them in. One self-contained binary. Open source. MIT-licensed.
- SMTP server on
127.0.0.1:3535— point any app at it; every message you send locally gets caught. - Web sandbox on
127.0.0.1:3550— search, view, inspect HTML/text/raw/headers/attachments. - REST API on
127.0.0.1:3550— JSON over HTTP; existing API clients only need a host override. - Validation tools: HTML email-client compatibility check across major mail clients.
- Workflow: categories, message release to production SMTP, outbound webhooks, sendmail-replacement binary mode.
Browse caught messages, preview rendered HTML, and inspect client compatibility — all offline, on your machine.
| Inbox + HTML preview | HTML Check | Getting started (code samples) |
|---|---|---|
![]() |
![]() |
![]() |
- Not a team tool. No user accounts, no authentication, no multi-tenancy, no shared sandboxes, no API tokens, no RBAC.
- Not infrastructure. No Postgres support, no high-availability, no Prometheus metrics, no Kubernetes probes. SQLite only. Localhost only.
- Not a Mailtrap Sandbox replacement. If you need team features and cloud setup, use Mailtrap — the hosted product this tool complements.
| Component | Technology |
|---|---|
| Single binary | Go 1.26+ — SMTP, HTTP/JSON API, embedded SPA, all in one process |
| SMTP ingest | emersion/go-smtp + jhillyerd/enmime |
| HTTP routing | go-chi/chi |
| Storage | SQLite via modernc.org/sqlite (pure Go — no CGo) |
| Web UI | React 19 + tailwindcss + Radix UI + Vite, embedded into the binary at build time via //go:embed |
| Realtime | Plain WebSocket (/cable) |
| Distribution | brew install, snap install, nix run, docker run, single static binary (macOS + Linux; Windows not yet) |
brew tap mailtrap/local
brew trust --formula mailtrap/local/mailtrap-local
brew install mailtrap-local
brew services start mailtrap-localThen open http://127.0.0.1:3550 and point your app's SMTP client at 127.0.0.1:3535.
sudo snap install mailtrap-local
mailtrap-localThen open http://127.0.0.1:3550 and point your app's SMTP client at 127.0.0.1:3535. Caught mail is stored in ~/snap/mailtrap-local/common/ (survives snap refreshes).
nix run github:mailtrap/mailtrap-nur#mailtrap-local
# or
nix profile add github:mailtrap/mailtrap-nur#mailtrap-local
# Nix older than 2.25: nix profile install …Package lives in mailtrap/mailtrap-nur; goreleaser opens a PR there on every tag (same flow as the Homebrew tap).
docker run --rm \
-p 127.0.0.1:3535:3535 -p 127.0.0.1:3550:3550 \
-v mailtrap-local:/var/lib/mailtrap-local \
mailtrap/mailtrap-local:latestmailtrap-local only accepts connections from your own machine. The image allows the container's own networks (--allow-attached-networks), since your machine's traffic reaches the container from the network's gateway and other containers come from inside it; publishing on 127.0.0.1 keeps the ports off your LAN. Don't run the image with --network host or on a macvlan or ipvlan network: the container's own subnet is then your LAN, so the LAN counts as an attached network.
With Docker Desktop and other VM-based engines (OrbStack, Colima, Podman Machine, …), ports published on all interfaces (-p 3550:3550) are refused even from your own machine: every connection, local or from the LAN, reaches the container from the VM's gateway, so the two can't be told apart. Publish on 127.0.0.1 as shown above.
The same image is also published to GHCR at ghcr.io/mailtrap/mailtrap-local:latest.
Download the right archive for your platform from the Releases page, extract, and run:
tar xzf mailtrap-local_<version>_<os>_<arch>.tar.gz
./mailtrap-localOn macOS, a one-time Gatekeeper bypass is required because the binary isn't notarized:
xattr -d com.apple.quarantine ./mailtrap-localgit clone https://github.com/mailtrap/mailtrap-local && cd mailtrap-local
scripts/build.sh # builds frontend + Go binary → bin/mailtrap-local
bin/mailtrap-localgo install github.com/mailtrap/mailtrap-local/cmd/mailtrap-local@latest also works for Go developers, but installs the build-time placeholder UI instead of the real React SPA — Vite isn't part of go install. Use one of the channels above for the real UI.
bin/mailtrap-local listens on 127.0.0.1:3535 (SMTP) and 127.0.0.1:3550 (HTTP+UI+API) by default. Pass --http-listen, --smtp-listen, --db, or --version to override. Connections from other machines are refused; --allow-from (comma-separated IPs or CIDR ranges) admits extra source addresses, and --allow-attached-networks admits the subnets of this machine's network interfaces.
Symlink (or rename) the binary to sendmail and put it on $PATH:
ln -sf "$(brew --prefix)/bin/mailtrap-local" /usr/local/bin/sendmailPHP's mail(), cron, mailx, and anything else that shells out to sendmail -t -i will hand the message off to the local sandbox. The binary detects it was invoked as sendmail (or mailtrap-sendmail, or via mailtrap-local sendmail …) and switches modes automatically.
# Prereqs: Go 1.26+, Node 22+, foreman (optional, for bin/dev)
# Install via mise/asdf using .tool-versions, or directly.
bin/setup # go mod download + npm install
bin/dev # boots backend + Vite concurrently (Procfile.dev)In dev:
| Port | Served by | Purpose |
|---|---|---|
3535 |
go run ./cmd/mailtrap-local |
SMTP ingest |
3540 |
Vite dev | SPA — proxies /api + /cable to backend |
3550 |
Go binary | JSON API + WebSocket |
Open http://127.0.0.1:3540 for the dev UI (with HMR). The production single-binary serves the same UI at http://127.0.0.1:3550 out of the embedded dist/.
mailtrap-local/
├── cmd/mailtrap-local/ # main + sendmail dispatch + embed staging
├── internal/
│ ├── api/ # chi handlers, wire types, SPA serving
│ ├── smtpd/ # go-smtp wrapper + enmime ingest
│ ├── store/ # SQLite schema + queries
│ ├── jobs/ # cloud mirror / relay / webhook / retention
│ ├── htmlcheck/ # HTML email compatibility rule engine
│ ├── live/ # in-process pub-sub for WebSocket
│ ├── relay/ # outbound SMTP relay client
│ ├── webhook/ # outbound webhook signed-POST client
│ ├── cloud/ # Mailtrap sandbox API client
│ └── config/ # YAML config loader
├── frontend/ # React + Vite SPA (built into cmd/mailtrap-local/dist/)
├── docs/
│ ├── api/openapi.yaml # API spec (embedded + served at /api/v1/openapi.yaml)
│ └── images/ # README screenshots + demo GIF
├── snap/gui/icon.png # Snap Store listing icon
├── scripts/build.sh # frontend build + go build → bin/mailtrap-local
├── Dockerfile # multi-stage source build (Node → Go → distroless)
├── Dockerfile.goreleaser # release-time wrapper (goreleaser-built binary → distroless)
├── .goreleaser.yaml # release pipeline: binaries + Homebrew + NUR + Snap + Docker Hub + GHCR
└── .github/workflows/ # ci.yml + release.yml
The Homebrew formula lives in mailtrap/homebrew-local; the Nix package lives in mailtrap/mailtrap-nur. goreleaser opens a PR in each on every tag, and also packs/uploads Snap Store packages.
The full OpenAPI 3.1 specification is at docs/api/openapi.yaml. When the app is running, the same spec is served at http://127.0.0.1:3550/api/v1/openapi.yaml. A bare GET on /api/v1 redirects to the hosted docs at https://docs.mailtrap.io/.
Optional YAML config at $XDG_CONFIG_HOME/mailtrap-local/config.yml (or ~/.config/mailtrap-local/config.yml). Sections:
storage.max_messages— retention cap (oldest evicted past the limit).cloud— Mailtrap sandbox API token + sandbox ID for the "Send to Cloud" button.relay— outbound SMTP relay credentials for the "Release" button.webhook— outbound webhook URL + signing secret.allowed_hosts— extra hostnames you open the UI/API with on this machine (e.g.john.devfrom/etc/hosts).localhostand*.localhostalways work.
Values support ${ENV_VAR} interpolation. Keys present in the config file override the equivalent values stored in the SQLite DB; pinned fields are read-only in the UI.
go test ./...Unit + integration tests cover the store, API, SMTP server, jobs dispatcher, HTML check, and sendmail mode. The OpenAPI drift test (internal/api/openapi_drift_test.go) fails the build if a route is added to chi without speccing it (or vice versa).
MIT — see LICENSE.



