Deployment guide

Inbucket

Overview

Inbucket is a popular open source, self-hosted disposable email server for development and testing. It runs an SMTP sink that accepts mail for any address, and lets you read and verify every captured message in a web interface and over a REST API. You can assert exactly what an application sent without ever delivering real email.

Inbucket is a single Go binary that serves the web UI, the REST API, an SMTP sink and an optional POP3 server. It keeps messages in its own on-disk store, so there is no external database to run.

By design, Inbucket has no authentication of its own, and anyone who can reach its web UI can read it. The SecureAnyCloud image secures it by default. The web UI and REST API bind to loopback, and nginx publishes them on port 80 behind HTTP Basic Auth. The password is generated uniquely on the first boot of every VM, so two VMs deployed from the same image never share a password, and there is no default login.

What you get:

  • Inbucket 3.1.1 (a single Go binary that bundles the web UI) at /opt/inbucket/inbucket
  • An nginx reverse proxy on :80 with per-VM HTTP Basic Auth, in front of the Inbucket web UI and REST API on loopback :9000
  • The SMTP sink on 0.0.0.0:2500, ready to receive test mail
  • POP3 bound to loopback 127.0.0.1:1100 (reachable over an SSH tunnel)
  • Inbucket's on-disk file store at /var/lib/inbucket (72-hour retention, 500 messages per mailbox)
  • A per-VM web password generated at first boot and stored in a root-only file, with no default login
  • inbucket.service and nginx.service as systemd units, enabled and active
  • A self-test, /usr/local/sbin/inbucket-selftest.sh, that sends a message through the sink and retrieves it through the API
  • A fully patched Ubuntu 24.04 LTS security baseline at capture time, with unattended security updates enabled
  • 24/7 SecureAnyCloud support

Understanding the access model

This image is deliberately locked down, because Inbucket itself has no login and captured mail can be sensitive. It has three listeners with three different exposures:

  • Web UI and REST API (port 80). nginx serves them with per-VM HTTP Basic Auth. They are safe to reach over the network, because a caller must supply the per-VM credential before seeing any mail. Inbucket's own web port (9000) is bound to loopback and never exposed directly.
  • SMTP sink (port 2500). Your application posts test mail here, which is the product's purpose. The sink accepts mail without authentication, as test mail servers do, so it ships closed at the network boundary. Open port 2500 deliberately, and only to the senders you trust (see Step 9). Never expose an open SMTP sink to the whole internet.
  • POP3 (port 1100). Bound to loopback only. If you use POP3, reach it over an SSH tunnel (see Step 10). It is never published to the network.

Prerequisites

You need an active Azure subscription, an SSH key pair, and a virtual network and subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a good starting size, because Inbucket is lightweight.

In your network security group, allow inbound 22/tcp from your management network and 80/tcp from the networks that will view captured mail. Open 2500/tcp only from the specific hosts or subnets that should post mail to the sink, and leave POP3 (1100) closed.

Step 1 - Deploy from the Azure Marketplace

Sign in to the Azure Portal, select Create a resource, search the Marketplace for Inbucket by SecureAnyCloud, and select Create.

On the Basics tab, choose your subscription, resource group, region and size. Under Administrator account, choose SSH public key and paste your key. Under Inbound port rules, allow SSH (22) and HTTP (80). Then select Review + create, followed by Create.

First-boot initialisation takes about a minute after the VM starts. Once the VM is up, add an inbound rule for 2500/tcp from your trusted senders.

Step 2 - Deploy from the Azure CLI

Accept the Marketplace terms once per subscription, create the VM, open port 80 for the web UI, and add a scoped rule for the SMTP sink:

# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:inbucket:default:latest
az vm create \
  --resource-group <your-rg> \
  --name inbucket \
  --image sac1647283583153:inbucket:default:latest \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_ed25519.pub \
  --vnet-name <your-vnet> --subnet <your-subnet> \
  --public-ip-sku Standard
# Web UI, reachable by anyone on this network but gated by per-VM basic auth:
az vm open-port --resource-group <your-rg> --name inbucket --port 80 --priority 1010
# SMTP sink: open 2500 ONLY to the senders you trust (replace the source prefix):
az network nsg rule create --resource-group <your-rg> --nsg-name inbucketNSG \
  --name allow-smtp-sink --priority 1020 --access Allow --protocol Tcp \
  --destination-port-ranges 2500 --source-address-prefixes <your-app-server-cidr>

Step 3 - Connect to your VM

Connect over SSH as the azureuser admin user:

ssh azureuser@<vm-public-ip>

Step 4 - Verify the Inbucket stack

Confirm that both services are active and check the port bindings. Only 80 and 2500 listen on all interfaces. The web port 9000 and POP3 1100 are bound to loopback:

systemctl is-active inbucket nginx ss -tln | grep -E ':(80|2500|9000|1100) ' || sudo ss -tln | grep -E ':(80|2500|9000|1100) '

Both lines read active. Ports 9000 and 1100 show 127.0.0.1, while 80 and 2500 listen on 0.0.0.0.

Step 5 - Retrieve your web sign-in

The web password generated on the first boot of your VM is stored in a root-only file, together with the URL and the SMTP sink details:

sudo cat /root/inbucket-credentials.txt

The username is admin. Store the password in your password manager, and treat the file as sensitive.

Step 6 - Confirm the web UI is authenticated

The front door rejects anonymous and wrong-password requests and accepts the generated password. The first two commands report 401, and the third reports 200:

curl -s -o /dev/null -w 'anonymous HTTP %{http_code}\n' http://127.0.0.1/
curl -s -o /dev/null -w 'wrong password HTTP %{http_code}\n' -u admin:wrong-password http://127.0.0.1/
PASS=$(sudo grep '^inbucket.web.pass=' /root/inbucket-credentials.txt | cut -d= -f2-)
curl -s -o /dev/null -w 'per-VM password HTTP %{http_code}\n' -u admin:$PASS http://127.0.0.1/

Step 7 - Sign in and view captured mail

Open a web browser and go to http://<vm-public-ip>/. When your browser prompts for the HTTP Basic Auth credential, sign in with the username admin and the password from /root/inbucket-credentials.txt.

To view a mailbox, type the local part of an address into the box at the top right (for example demo for demo@inbucket.local) and select View. Every message sent to that address is listed, newest first.

Select a message to open it. Inbucket shows the sandboxed HTML body, the plain-text alternative, the parsed headers, the raw source and any attachments, so you see everything your application actually sent.

Step 8 - Send a test message and read it back

Point any SMTP client at the sink on port 2500. From the VM itself, you can send a message with Python and then read it back through the authenticated REST API:

python3 - <<'PY' import smtplib from email.message import EmailMessage m = EmailMessage() m["From"] = "app@example.test" m["To"] = "welcome@inbucket.local" m["Subject"] = "Verify your email" m.set_content("Your verification code is 550132.") s = smtplib.SMTP("127.0.0.1", 2500, timeout=15) s.send_message(m) s.quit() print("sent") PY

Mailboxes are named by the local part of the address, so the message lands in the welcome mailbox. Retrieve it over the REST API, which sits behind the same Basic Auth. The list endpoint returns message summaries, and the show endpoint returns the full body:

PASS=$(sudo grep '^inbucket.web.pass=' /root/inbucket-credentials.txt | cut -d= -f2-)
ID=$(curl -s -u admin:$PASS http://127.0.0.1/api/v1/mailbox/welcome | jq -r '.[0].id')
curl -s -u admin:$PASS http://127.0.0.1/api/v1/mailbox/welcome/$ID | jq '{from, subject, body: .body.text}'

The bundled self-test runs the whole round trip for you. It sends a uniquely tagged message through the sink and checks that the API returns the matching body:

sudo bash /usr/local/sbin/inbucket-selftest.sh

Step 9 - Point your application at the SMTP sink

In your application or test suite, set the SMTP host to your VM's address and the port to 2500. Use no TLS and no authentication:

SMTP host: <vm-public-ip> SMTP port: 2500

Mail from another host reaches the sink only if you opened 2500/tcp in the network security group for that source (Step 2). Keep that rule scoped to the specific senders that should post mail, because an SMTP sink open to the whole internet attracts spam and abuse.

Inbucket accepts and stores everything it receives, which makes it a good fit for CI. Run your application against it, then assert over the REST API that the expected mail arrived.

Step 10 - POP3 access over an SSH tunnel (optional)

POP3 is bound to loopback and is never exposed to the network. If a tool needs to fetch messages over POP3, forward the port over SSH from your workstation:

ssh -L 1100:127.0.0.1:1100 azureuser@<vm-public-ip>
# then point your POP3 client at localhost:1100 (mailbox = the local part of the address)

Security baseline

The image ships with the Ubuntu 24.04 security baseline fully applied, including phased updates. Unattended security upgrades are enabled, so your VM keeps patching itself after deployment:

cat /etc/apt/apt.conf.d/20auto-upgrades

Enabling HTTPS

For any use over an untrusted network, serve the web UI over HTTPS so that the Basic Auth credential and captured mail are encrypted in transit. Point a DNS A record at the VM's public IP, then install a certificate with Certbot, replacing the example domain with yours:

sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.example.com

After you enable HTTPS, open 443/tcp in the network security group, and restrict or close inbound port 80.

Maintenance and message retention

Inbucket keeps messages in its file store at /var/lib/inbucket. It retains them for 72 hours and caps each mailbox at 500 messages, and it removes older messages automatically.

To change these limits, edit /etc/inbucket/inbucket.env (for example INBUCKET_STORAGE_RETENTIONPERIOD and INBUCKET_STORAGE_MAILBOXMSGCAP), then restart with sudo systemctl restart inbucket.

Apply operating system security updates with sudo apt update && sudo apt upgrade. The service restarts cleanly.

To rotate the web password yourself, run sudo htpasswd -b /etc/nginx/inbucket.htpasswd admin '<new-password>' and sudo systemctl reload nginx. Then record the new password in /root/inbucket-credentials.txt.

Support

This image is backed by 24/7 SecureAnyCloud support. Contact us by email or chat for help with deployment, integration with your test suites, TLS termination and network scoping of the SMTP sink.

Inbucket is open source software licensed under the MIT license.

All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.

Deploy on Azure

Launch Inbucket on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure