Testing email is one of those tasks that sounds simple until a dev environment accidentally sends real password resets, invoices, account invites, or notification spam.

Inbucket gives you a safer target.

Inbucket is an email testing service; it will accept messages for any email address and make them available via web, REST and POP3 interfaces.

It behaves like a disposable mailbox service for your own development stack.

Your app sends SMTP to Inbucket, and you inspect the result in a browser or through an API.

What is Inbucket?

Inbucket is a self-hosted email testing server.

It includes:

  • SMTP intake for test applications.
  • A web UI for browsing mailboxes.
  • REST endpoints for automated tests.
  • POP3 access for mail clients and compatibility checks.
  • A monitor view for recently received messages.
  • Memory or file-backed storage.
  • Retention cleanup so old test messages do not pile up forever.
  • Lua extension hooks for advanced policy/customization.

The key difference from a real mail server is intent.

Inbucket is not trying to deliver mail to the internet.

It is trying to catch mail before it leaves your test environment.

That makes it useful for staging apps, local development, CI, QA demos, and homelab services where you want to verify email behavior without involving Gmail, Exchange, Mailgun, SES, Postmark, or real customer inboxes.

Why Self-Host Inbucket?

Self-hosting Inbucket gives you a local SMTP target that is easy to reason about.

Point your application at:

SMTP host: your-server-ip
SMTP port: 2525
TLS: off for local testing, or configure explicitly
Auth: usually not required for dev-only use

Then open the matching mailbox in the web UI.

If your app sends to [email protected], the default mailbox naming mode stores it under alice.

If you want domain-aware mailboxes, configure INBUCKET_MAILBOXNAMING=full.

This is much safer than testing against real outbound SMTP.

You can inspect HTML, plain text, headers, attachments, and application-generated links without sending anything to a real user.

alt text

Tech Overview

Inbucket is written in Go with an Elm frontend.

The backend includes built-in servers for:

  • HTTP web UI and REST API.
  • SMTP.
  • POP3.
  • Storage retention scanning.
  • Message monitoring.

The Docker image ships the compiled Go daemon plus the built frontend assets.

In Docker, the image defaults to file storage under /storage, while the native binary defaults to memory storage unless configured otherwise.

The default internal ports are:

Service Internal Port
Web UI and REST API 9000
SMTP 2500
POP3 1100

The REST API is mounted under /api/. For example:

curl http://localhost:9325/api/v1/mailbox/alice

Self-Hosting Inbucket with Docker

The upstream README shows a direct Docker command:

docker run -d --name inbucket \
  -p 9000:9000 \
  -p 2500:2500 \
  -p 1100:1100 \
  inbucket/inbucket

That works, but I prefer not to bind default ports on a homelab server unless I know they are free.

For this field test, I used alternate host ports:

  • Web UI: 9325 -> 9000
  • SMTP: 2525 -> 2500
  • POP3: 1115 -> 1100

alt text

services:
  inbucket:
    image: inbucket/inbucket:latest
    container_name: inbucket-safe
    restart: unless-stopped
    network_mode: bridge
    ports:
      - "9325:9000"  # Web UI and REST API
      - "2525:2500"  # SMTP intake
      - "1115:1100"  # POP3
    environment:
      INBUCKET_LOGLEVEL: info
      INBUCKET_STORAGE_TYPE: file
      INBUCKET_STORAGE_PARAMS: path:/storage
      INBUCKET_STORAGE_RETENTIONPERIOD: 72h
      INBUCKET_STORAGE_MAILBOXMSGCAP: "300"
      INBUCKET_SMTP_TIMEOUT: 30s
      INBUCKET_POP3_TIMEOUT: 30s
      INBUCKET_WEB_MONITORVISIBLE: "true"
    volumes:
      - inbucket-safe-config:/config
      - inbucket-safe-storage:/storage

volumes:
  inbucket-safe-config:
  inbucket-safe-storage:

Start it:

docker compose -f docker-compose.yml -p inbucket-safe up -d

Access it:

Web UI: http://localhost:9325/
SMTP:   localhost:2525
POP3:   localhost:1115
REST:   http://localhost:9325/api/v1/mailbox/{mailbox}

alt text

From another machine on your LAN, replace localhost with your server IP.

Field Note: Docker Network Pools

On my server, the first Compose attempt failed before creating the container:

all predefined address pools have been fully subnetted

That means Docker could not allocate another user-defined bridge network. For this single-container app, the safe workaround was to use Docker’s existing bridge network:

network_mode: bridge

On a normal Docker host, you can remove that line and let Compose create its default network. I kept it in the tested snippet because it avoided touching existing homelab containers and worked on this server.

Testing SMTP Capture

After starting the stack, send a test email:

python3 - <<'PY'
import smtplib
from email.message import EmailMessage

msg = EmailMessage()
msg["From"] = "[email protected]"
msg["To"] = "codex@localhost"
msg["Subject"] = "Inbucket smoke test from Codex"
msg.set_content("This message verifies SMTP capture through Inbucket.")

with smtplib.SMTP("localhost", 2525, timeout=10) as smtp:
    smtp.send_message(msg)
PY

Then query the mailbox:

curl http://localhost:9325/api/v1/mailbox/codex

In my field test, the API returned the captured message with this subject:

Inbucket smoke test from Codex

The container also became healthy and /debug/vars reported one received SMTP message.

Important Configuration

Most Inbucket configuration is done through environment variables.

For a small homelab or dev server, these are the ones I would review first:

Variable Why It Matters
INBUCKET_STORAGE_TYPE Use file if you want messages to survive restarts
INBUCKET_STORAGE_PARAMS Set path:/storage when using Docker volumes
INBUCKET_STORAGE_RETENTIONPERIOD Controls how long messages are kept
INBUCKET_STORAGE_MAILBOXMSGCAP Prevents one mailbox from growing forever
INBUCKET_MAILBOXNAMING Choose local, full, or domain mailbox naming
INBUCKET_SMTP_DEFAULTACCEPT Accept everything by default, or only configured domains
INBUCKET_SMTP_ACCEPTDOMAINS Allow-list recipient domains
INBUCKET_SMTP_REJECTDOMAINS Deny-list recipient domains
INBUCKET_SMTP_REJECTORIGINDOMAINS Deny-list sender origin domains
INBUCKET_WEB_BASEPATH Required when serving the UI under a reverse-proxy subpath
INBUCKET_WEB_PPROF Keep false unless debugging privately

For a controlled staging environment, consider:

environment:
  INBUCKET_SMTP_DEFAULTACCEPT: "false"
  INBUCKET_SMTP_ACCEPTDOMAINS: "example.test,staging.local"

That avoids turning the service into an open catch-all for any recipient domain.

Safe Exposure and Reverse Proxy Notes

Inbucket’s UI is intentionally simple. It does not provide a full user account or mailbox authentication system.

Do not expose it publicly as-is.

If you want remote browser access, put authentication in front of the web UI with something like:

  • Cloudflare Access.
  • Authelia.
  • Authentik.
  • Traefik middleware.
  • Caddy auth.
  • VPN-only access.

Cloudflare Tunnel can expose the web UI, but it does not expose generic SMTP or POP3 ports. Your apps still need a private path to the SMTP listener, such as the same Docker host, Docker network, LAN, or VPN.

Building Inbucket from Source

For source builds, you need Go plus Node/Yarn for the UI:

git clone https://github.com/inbucket/inbucket.git
cd inbucket/ui
yarn install
yarn build
cd ..
go build ./cmd/inbucket

In my local analysis, a backend build succeeded with:

GOFLAGS=-mod=mod go build -o ../inbucket-field-test ./cmd/inbucket

The resulting local binary was about 18 MB on Linux amd64. Because I did not inject release linker flags, inbucket -version reported undefined; the release build pipeline sets that metadata.

When Inbucket Fits

Pick Inbucket if you want:

  • A lightweight SMTP sink for dev and staging.
  • Disposable mailboxes without account setup.
  • API-driven email assertions in integration tests.
  • A simple web UI for reviewing transactional emails.
  • POP3 compatibility for email-client testing.
  • A self-contained service without PostgreSQL, Redis, or an external MTA.

Use something else if you need:

  • Real inbound/outbound email hosting.
  • User accounts and mailbox permissions.
  • Long-term archival.
  • Spam filtering.
  • DKIM, DMARC, SPF, and delivery reputation management.

Inbucket is best understood as test infrastructure, not production mail infrastructure.

Conclusion

Inbucket is a practical tool for self-hosted email testing.

It gives developers and QA teams a real SMTP target, a browsable mailbox UI, and a REST API for automation, while keeping test emails inside your own environment. The Docker path is simple, and the service does not need a separate database.

For homelab use, the main rule is exposure control: keep SMTP and POP3 private, protect the web UI if it leaves your LAN, and set retention limits so captured messages do not accumulate forever.

The project is open source under the MIT License. ❤️

FAQ