smtp4dev 3.15.0
Overview
smtp4dev is an open source fake SMTP server for development and testing. Point the applications you are building or testing at it instead of a real mail server. It accepts every message, keeps it and shows it in a web inbox. It never delivers anything to a real mailbox, so you can exercise password resets, order confirmations and notification digests safely against realistic data.
This guide covers launching and using the smtp4dev 3.15.0 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud Mail Servers and Newsletters listing on AWS Marketplace. You pick it as the software version when you subscribe and launch.
In the web inbox, you can read the rendered HTML, the plain text and the raw MIME source, check headers and attachments, and see HTML validation and email client compatibility warnings. A REST API exposes the same messages to your test suites.
What you get:
- SMTP that is not an open mail sink. smtp4dev listens for SMTP on port 2525 and requires SMTP authentication with the login generated for your instance. A sender with no credentials or the wrong ones is refused. STARTTLS is offered with your instance's own certificate, and plain text passwords (PLAIN and LOGIN) are accepted only after STARTTLS. Anonymous and OAuth logins are not offered. Port 2525 also avoids the restriction EC2 places on port 25.
- No relaying, no IMAP, no POP3. smtp4dev can forward captured mail to a real server and can serve it over IMAP and POP3. On this image, relaying is not configured and the settings are locked so that the web UI and API cannot configure it. IMAP and POP3 are off. Upstream's IMAP server accepts any user name and password unless authentication is required, which would expose every captured message.
- HTTPS from the first boot. nginx serves the web inbox on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS.
- A login unique to your instance. The user
adminand a password generated on first boot protect the web inbox, the REST API, the live update channel and the API documentation, and they authenticate SMTP. The password is written to/root/smtp4dev-credentials.txt(root only). nginx keeps a bcrypt hash of it, and smtp4dev checks it again behind nginx. No password, settings file or key is baked into the image, and neither nginx nor smtp4dev starts until first boot has created them. - Loopback only and locked down. The smtp4dev web app listens on
127.0.0.1:5000behind nginx. It runs as its own unprivileged user with no capabilities, in a systemd sandbox where its settings and certificate are read-only. smtp4dev and nginx cannot reach the EC2 instance metadata service. - Hardened defaults. The AMI requires IMDSv2. Requests for a host name the instance does not serve are dropped. Another web site cannot drive the inbox with your browser's saved login. nginx passes smtp4dev the real client address, not one a client claims.
- A dedicated data volume. Captured messages live in SQLite at
/var/lib/smtp4devon their own 20 GiB gp3 EBS volume, which you can resize independently. - Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on. They also patch the ASP.NET Core runtime that smtp4dev runs on.
What is on the image: smtp4dev 3.15.0 (the official release build) at /opt/smtp4dev on the Ubuntu ASP.NET Core 10 runtime, nginx, swaks (a command line SMTP client), Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.
Default user: ubuntu. Architecture: x86_64. Recommended instance type: m5.large.
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet
- A security group that allows TCP 22 (SSH), TCP 443 (the web inbox) and TCP 80 (redirects to HTTPS) from your own networks, and TCP 2525 (SMTP) from the networks or security groups of the applications that will send mail to smtp4dev. Keep all four closed to the rest of the internet.
Launch the instance
In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick smtp4dev 3.15.0 on Ubuntu 24.04 as the software version, choose your Region and choose Launch through EC2. Select m5.large, your key pair and a security group as described above.
The AMI includes the 20 GiB data volume for captured mail. Increase its size on the launch screen if you keep a lot of mail.
To launch with the AWS CLI instead, replace the values in angle brackets:
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=smtp4dev}]'Connect to your instance
Version SSH user
smtp4dev 3.15.0 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>Check the services
First boot takes a few seconds. When it finishes, the three units are active:
systemctl is-active smtp4dev nginx smtp4dev-firstbootactive active activesmtp4dev (a .NET process) serves the web app on loopback only and SMTP on 2525. nginx serves ports 80 and 443. Nothing listens on 25, 143 (IMAP) or 110 (POP3):
sudo ss -tlnp | awk 'NR==1 || /dotnet|nginx|sshd/' | awk '{print $1, $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g'State Local Peer LISTEN 0.0.0.0:443 users:(("nginx"),("nginx"),("nginx")) LISTEN 0.0.0.0:80 users:(("nginx"),("nginx"),("nginx")) LISTEN 0.0.0.0:22 users:(("sshd"),("systemd")) LISTEN 127.0.0.1:5000 users:(("dotnet")) LISTEN *:2525 users:(("dotnet")) LISTEN [::]:443 users:(("nginx"),("nginx"),("nginx")) LISTEN [::]:80 users:(("nginx"),("nginx"),("nginx")) LISTEN [::]:22 users:(("sshd"),("systemd"))cat /opt/smtp4dev/.sac-version dotnet --list-runtimes | grep AspNetCore findmnt -no TARGET,SOURCE,FSTYPE,SIZE /var/lib/smtp4dev3.15.0 Microsoft.AspNetCore.App 10.0.12 [/usr/lib/dotnet/shared/Microsoft.AspNetCore.App] /var/lib/smtp4dev /dev/nvme1n1 ext4 19.5GGet your URL, SMTP host and password
sudo cat /root/smtp4dev-credentials.txtThe file holds the address that smtp4dev is served as (your instance's public DNS name by default), the SMTP host and port, the user and the password generated on this instance:
SMTP4DEV_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ SMTP4DEV_USERNAME=admin SMTP4DEV_PASSWORD=<24 letters and digits> SMTP4DEV_SMTP_HOST=ec2-<public-ip-dashed>.<region>.compute.amazonaws.com SMTP4DEV_SMTP_PORT=2525The same user and password sign in to the web inbox and authenticate SMTP.
Send your first message
swaks is installed for sending test messages. This command sends a message to smtp4dev over STARTTLS with SMTP authentication. It reads the password from the credentials file into an environment variable that swaks reads, so the password never appears on a command line:
export SWAKS_OPT_auth_password="$(sudo sed -n 's/^SMTP4DEV_PASSWORD=//p' /root/smtp4dev-credentials.txt)" swaks --server 127.0.0.1:2525 --tls --auth LOGIN --auth-user admin \
--from app@example.com --to jane.doe@example.com \
--header 'Subject: Hello from swaks' --body 'My first captured message' | grep -E '^ ?(<~|~>) +(AUTH|235|250 Mail)' unset SWAKS_OPT_auth_password~> AUTH LOGIN <~ 235 Authenticated OK <~ 250 Mail acceptedsmtp4dev accepted the message and kept it. It did not deliver the message to jane.doe@example.com.
Read captured mail in your browser
Open SMTP4DEV_URL in your browser. The certificate is self-signed (generated on your instance), so accept the browser warning once, or install your own certificate (Step 12). Your browser then asks for a user name and password. Enter admin and the password from Step 4.
smtp4dev opens on the inbox, with every captured message listed by time, sender, recipient and subject. New messages appear as they arrive. Select one to see it rendered as a mail client would show it, with the From, To and Subject at the top. smtp4dev sanitizes HTML for display by default, so a hostile message cannot run script in your browser.
Use the tabs to inspect a message:
- Source shows the exact MIME your application produced.
- Headers lists every header.
- Parts breaks out the alternative bodies and attachments.
- Analysis reports HTML validation problems and features that common email clients do not support.
The Sessions tab keeps the full SMTP conversation of each connection, which is useful when a client fails to send. In a session, you can see that the client asked for STARTTLS, saw the extra login methods offered after it, and authenticated before sending.
Read captured mail through the API
Every view in the web inbox is backed by a JSON REST API under /api/. The same login protects it, and it is handy for asserting on mail in automated tests.
This helper reads the password from the credentials file and hands it to curl on stdin, so the password never appears on a command line:
H=$(sudo sed -n 's/^SMTP4DEV_SMTP_HOST=//p' /root/smtp4dev-credentials.txt) s4d_api() {
sudo sed -n 's/^SMTP4DEV_PASSWORD=\(.*\)$/user = "admin:\1"/p' /root/smtp4dev-credentials.txt | curl -sk -K - --resolve "$H:443:127.0.0.1" "https://$H/api/$1"; } s4d_api 'Messages?pageSize=5' | jq -r '.results[] | [.from, (.to | join(",")), .subject] | @tsv'app@example.com jane.doe@example.com Hello from swaks/api/Messages/<id> returns a message's details (headers, parts, attachments), and /api/Messages/<id>/source returns its raw MIME. DELETE /api/Messages/* empties the inbox between test runs.
The full API description is at https://<your host>/api/index.html once you are signed in. From another machine, use curl -u admin https://<your host>/api/Messages. curl then prompts for the password.
Point your application at smtp4dev
Use these settings in the application under test:
Setting Value
SMTP host SMTP4DEV_SMTP_HOST from Step 4, or the instance's private IP from inside your VPC
Port 2525
Security STARTTLS (the certificate is self signed: turn certificate verification off for this server, or install your own certificate, Step 12)
Authentication user admin, password SMTP4DEV_PASSWORD (PLAIN or LOGIN after STARTTLS, or CRAM-MD5)
For example, in Python:
import smtplib, ssl ctx = ssl.create_default_context(); ctx.check_hostname = False; ctx.verify_mode = ssl.CERT_NONE with smtplib.SMTP("<smtp4dev-host>", 2525) as s: s.starttls(context=ctx) s.login("admin", "<SMTP4DEV_PASSWORD>") s.sendmail("app@example.com", ["jane.doe@example.com"], "Subject: Test\r\n\r\nHello")and in Node.js with nodemailer:
const transport = nodemailer.createTransport({ host: "<smtp4dev-host>", port: 2525, secure: false, requireTLS: true, tls: { rejectUnauthorized: false }, auth: { user: "admin", pass: process.env.SMTP4DEV_PASSWORD }, });Keep the password in your application's secret store, not in source control. If you allowed 2525 only from your application's security group, nothing else can reach the port.
Security checks
smtp4dev refuses a sender that does not log in before the sender can hand over a message:
OUT=$(swaks --server 127.0.0.1:2525 --from app@example.com --to jane.doe@example.com --quit-after RCPT || true)
grep -E '^<\*\*|^ ?<- +250 AUTH' <<<"$OUT"<- 250 AUTH CRAM-MD5 <** 530 Authentication is requiredBefore STARTTLS, smtp4dev offers only CRAM-MD5, which never sends the password.
Port 80 sends browsers to HTTPS. The health endpoint answers without a password for load balancer checks, but the inbox and the API do not. A host name the instance does not serve gets no answer (000):
H=$(sudo sed -n 's/^SMTP4DEV_SMTP_HOST=//p' /root/smtp4dev-credentials.txt)
curl -s -o /dev/null -w 'port 80: %{http_code} -> %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/healthz"
curl -sk -o /dev/null -w 'web inbox without a password: %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/"
curl -sk -o /dev/null -w 'API without a password: %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/api/Messages"
curl -s -o /dev/null -w 'unknown host name: %{http_code}\n' -H 'Host: example.com' http://127.0.0.1/ || trueport 80: 301 -> https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ ok web inbox without a password: 401 API without a password: 401 unknown host name: 000Only root and the service that needs them can read the settings, the password hash and the TLS key. Only root can read the credentials file. The certificate (used for HTTPS and for SMTP STARTTLS) was generated on this instance and names its own addresses:
sudo stat -c '%a %U:%G %n' /etc/smtp4dev/appsettings.json /etc/nginx/smtp4dev.htpasswd /etc/ssl/smtp4dev/smtp4dev.key /root/smtp4dev-credentials.txt echo | openssl s_client -connect 127.0.0.1:2525 -starttls smtp 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName640 root:smtp4dev /etc/smtp4dev/appsettings.json 640 root:www-data /etc/nginx/smtp4dev.htpasswd 640 root:smtp4dev /etc/ssl/smtp4dev/smtp4dev.key 600 root:root /root/smtp4dev-credentials.txt subject=CN = ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, O = sac X509v3 Subject Alternative Name: DNS:ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, IP Address:<public-ip>, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhostBrowsers resend a saved login on requests that other sites trigger. For that reason, nginx refuses any change request or WebSocket whose Origin is another site (HTTP 403).
Locked settings and no relaying
smtp4dev reads its settings from /etc/smtp4dev/appsettings.json, which the service can read but not change. Relaying is not configured, IMAP and POP3 are off, SMTP authentication is required and the settings are locked. The Settings dialog in the web inbox therefore shows them read-only, and the API refuses changes:
H=$(sudo sed -n 's/^SMTP4DEV_SMTP_HOST=//p' /root/smtp4dev-credentials.txt)
sudo python3 -c 'import json; d=json.load(open("/etc/smtp4dev/appsettings.json")); s=d["ServerOptions"]; print({k: s[k] for k in ("Port","ImapPort","Pop3Port","AuthenticationRequired","SmtpAllowAnyCredentials","TlsMode","LockSettings")}); print("Relay server:", repr(d["RelayOptions"]["SmtpServer"]))'
sudo sed -n 's/^SMTP4DEV_PASSWORD=\(.*\)$/user = "admin:\1"/p' /root/smtp4dev-credentials.txt | curl -sk -K - --resolve "$H:443:127.0.0.1" -X POST -H "Origin: https://$H" -H 'Content-Type: application/json' -d '{"relaySmtpServer":"smtp.example.com"}' -w ' (HTTP %{http_code})\n' "https://$H/api/Server"{'Port': 2525, 'ImapPort': None, 'Pop3Port': None, 'AuthenticationRequired': True, 'SmtpAllowAnyCredentials': False, 'TlsMode': 'StartTls', 'LockSettings': True} Relay server: '' Settings are locked (HTTP 401)smtp4dev keeps the newest 100 messages and 100 SMTP sessions and accepts messages of up to 25 MiB. To change these or other options, edit /etc/smtp4dev/appsettings.json as root and run sudo systemctl restart smtp4dev. The option names are documented in /opt/smtp4dev/appsettings.json.
smtp4dev-set-password and smtp4dev-set-hostname rewrite the file with the SecureAnyCloud defaults, so re-apply your edits after running them.
What smtp4dev can reach
smtp4dev runs as the unprivileged smtp4dev user with no capabilities and a read-only view of the system. The kernel refuses its connections, and those of nginx, to the EC2 instance metadata service:
systemctl show smtp4dev -p User -p ProtectSystem -p NoNewPrivileges -p CapabilityBoundingSet -p IPAddressDeny
systemctl show nginx -p IPAddressDenyIPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128 CapabilityBoundingSet= User=smtp4dev ProtectSystem=strict NoNewPrivileges=yes IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128Your own DNS name and certificate
Point a DNS record at the instance, then set the name that smtp4dev is served as. The command mints a new self-signed certificate for the name (used for HTTPS and for SMTP STARTTLS). It updates nginx, the SMTP greeting and the credentials file, restarts smtp4dev and keeps the name across reboots:
sudo smtp4dev-set-hostname <your-domain>To use a certificate from your own certificate authority instead, copy it over the generated pair. Then reload nginx and restart smtp4dev. Running smtp4dev-set-hostname again replaces your certificate with a self-signed one:
sudo install -o root -g root -m 0644 <your-domain>.crt /etc/ssl/smtp4dev/smtp4dev.crt
sudo install -o root -g smtp4dev -m 0640 <your-domain>.key /etc/ssl/smtp4dev/smtp4dev.key
sudo nginx -t && sudo systemctl reload nginx && sudo systemctl restart smtp4devChange the password
smtp4dev-set-password sets a new password for admin (web inbox, API and SMTP), records it in the credentials file, reloads nginx and restarts smtp4dev. Run it without arguments for a new random password, or pass your own (16 to 64 letters and digits) on stdin.
Update the applications that send mail with the new password.
sudo smtp4dev-set-password printf '%s\n' '<your-new-password>' | sudo smtp4dev-set-password --stdinData, backups and upgrades
Captured mail is in the SQLite database /var/lib/smtp4dev/database.db on the separate data volume, so you can resize or snapshot that volume on its own. smtp4dev is a test tool and keeps only the newest messages. Snapshot the volume if you want to keep a copy.
The image ships the version named in the listing. When SecureAnyCloud publishes a newer release, launch the newer version from the listing to move to it.
Server components
Component Path
smtp4dev release /opt/smtp4dev
Settings (read only to the service) /etc/smtp4dev/appsettings.json (root:smtp4dev, 0640)
Message store (data volume) /var/lib/smtp4dev
Service unit and metadata drop-in /etc/systemd/system/smtp4dev.service, /etc/systemd/system/smtp4dev.service.d/
nginx site /etc/nginx/sites-available/sac-smtp4dev
Password hash (bcrypt) /etc/nginx/smtp4dev.htpasswd (root:www-data, 0640)
Certificate and key /etc/ssl/smtp4dev/
First boot script /usr/local/sbin/smtp4dev-firstboot.sh
Host name and password helpers /usr/local/sbin/smtp4dev-set-hostname, /usr/local/sbin/smtp4dev-set-password
Credentials /root/smtp4dev-credentials.txt (mode 0600)
Security recommendations
- Allow 2525 only from the networks or security groups of the applications under test, and allow 22, 80 and 443 only from your own networks.
- Treat captured mail as sensitive. It contains whatever your applications sent, including password reset links and personal data from test fixtures. Empty the inbox between test runs (
DELETE /api/Messages/*). - Keep the password secret, and rotate it if it may have been shared (Step 13).
- Use your own DNS name and a certificate your clients trust (Step 12).
- Keep the operating system patched. Unattended security upgrades are on, and
sudo apt-get update && sudo apt-get upgradeapplies everything else.
Support and licensing
smtp4dev is open source under the BSD 3-Clause license (a copy is at /usr/share/doc/smtp4dev/LICENSE). There is no per-user or per-CPU license fee. The AMI price covers SecureAnyCloud's hardening, first-boot setup and support.
- Email: sales@secureanycloud.com
- Website: https://secureanycloud.com
- Support: 24/7, with a 24 hour response SLA
Deploy on AWS
Launch smtp4dev 3.15.0 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com