How to Set Up a Fake SMTP Server for Development
Every application that sends email needs a way to test those emails during development. Pointing your dev environment at a real SMTP server like Gmail or SendGrid is a mistake -- you risk sending test emails to real users, burning through API quotas, and leaking test data.
A fake SMTP server solves this. It accepts emails over the standard SMTP protocol, captures them, and lets you inspect them -- without delivering anything to the outside world.
This guide covers three approaches, from fully local to cloud-hosted, with step-by-step setup instructions and framework configuration for Laravel, Django, Rails, and Node.js.
What a Fake SMTP Server Actually Does
A fake SMTP server implements the SMTP protocol (typically on port 1025 or 2525 instead of the standard 25/587) and accepts any email sent to it. Instead of relaying the message to a recipient's mail server, it stores the email locally and makes it available for inspection.
From your application's perspective, nothing changes. It connects to an SMTP host, authenticates (or not), sends a message, and gets a success response. The application has no idea the email was captured instead of delivered.
Most fake SMTP servers provide:
- A web interface to browse captured emails
- The ability to view both HTML and plain-text versions
- Raw message source and header inspection
- An API for programmatic access (useful in CI/CD)
Option 1: Mailpit (Local, Self-Hosted)
Mailpit is a lightweight, open-source fake SMTP server written in Go. It is fast, has zero dependencies, and provides a clean web UI.
Installing Mailpit with Docker
The fastest way to run Mailpit:
docker run -d \
--name mailpit \
-p 1025:1025 \
-p 8025:8025 \
axllent/mailpit
This gives you:
- SMTP server on
localhost:1025 - Web UI on
http://localhost:8025
Installing Mailpit as a Binary
If you prefer not to use Docker, download the binary for your platform from the Mailpit releases page:
# macOS (Homebrew)
brew install mailpit
# Start it
mailpit
By default, it listens on 0.0.0.0:1025 (SMTP) and 0.0.0.0:8025 (HTTP).
Docker Compose Setup
For projects that already use Docker Compose, add Mailpit as a service:
# docker-compose.yml
services:
app:
build: .
ports:
- "8000:8000"
environment:
SMTP_HOST: mailpit
SMTP_PORT: 1025
depends_on:
- mailpit
mailpit:
image: axllent/mailpit:latest
ports:
- "1025:1025" # SMTP
- "8025:8025" # Web UI
volumes:
- mailpit-data:/data
environment:
MP_MAX_MESSAGES: 5000
MP_DATABASE: /data/mailpit.db
volumes:
mailpit-data:
Key points:
- Your application connects to
mailpit:1025(the Docker service name resolves internally). - The web UI is accessible on your host at
http://localhost:8025. - The
MP_MAX_MESSAGESsetting prevents the database from growing unbounded. - The volume persists emails across container restarts.
When to use Mailpit: You are working solo, offline, or want a zero-dependency local setup. It is fast and reliable for individual development.
Limitation: Each developer runs their own instance. There is no built-in way to share captured emails across a team.
Option 2: SendPit (Cloud-Hosted, Zero Setup)
SendPit is a cloud-hosted SMTP sandbox designed for development teams. You sign up, get SMTP credentials, and point your application at them. No infrastructure to manage.
Getting Started
- Create a free account at sendpit.com.
- Create a mailbox in the dashboard. You will get SMTP credentials (host, port, username, password).
- Configure your application with those credentials.
MAIL_HOST=smtp.sendpit.com
MAIL_PORT=1025
MAIL_USERNAME=mb_abc123def456
MAIL_PASSWORD=your_generated_password
That is it. Every email your application sends now appears in the SendPit dashboard.
Shared Mailboxes for Teams
The primary advantage over local solutions: shared visibility. When you create a mailbox in SendPit, you can invite team members. A developer triggers a transactional email in a staging environment, and a QA engineer on the other side of the office (or the world) can immediately inspect it.
This eliminates the "it works on my machine" problem for email testing. Everyone sees the same captured messages.
API Access for CI/CD
SendPit provides an API to query captured emails, which makes it straightforward to verify email content in automated tests:
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://api.sendpit.com/v1/mailbox/{id}/[email protected]"
This is particularly useful in CI pipelines where there is no human to open a web UI.
When to use SendPit: Your team needs shared access to test emails, you want zero infrastructure overhead, or you need a stable SMTP endpoint for CI/CD pipelines.
Option 3: Python's Built-in SMTP Server (Quick and Dirty)
Python ships with a debugging SMTP server that prints emails to the console. It requires nothing beyond a Python installation.
# Python 3
python3 -m smtpd -n -c DebuggingServer localhost:1025
Or, using the newer aiosmtpd package (recommended, as smtpd is deprecated in Python 3.12+):
pip install aiosmtpd
python3 -m aiosmtpd -n -l localhost:1025
This dumps every received email to stdout. No web UI, no search, no API -- just raw output in your terminal.
When to use this: You need to quickly verify that your application is sending SMTP traffic at all, or you are debugging connection issues. Do not use this as your primary development email tool -- there is no way to browse, search, or inspect HTML rendering.
Configuring Popular Frameworks
Once your fake SMTP server is running, you need to point your application at it. Here is how to do it in the most common frameworks.
Laravel
Laravel's mail configuration lives in config/mail.php, but you override it via environment variables. In your .env file:
MAIL_MAILER=smtp
MAIL_HOST=localhost
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="[email protected]"
MAIL_FROM_NAME="${APP_NAME}"
For SendPit (which requires authentication):
MAIL_MAILER=smtp
MAIL_HOST=smtp.sendpit.com
MAIL_PORT=1025
MAIL_USERNAME=mb_your_credential
MAIL_PASSWORD=your_password
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="[email protected]"
MAIL_FROM_NAME="${APP_NAME}"
Send a test email from Tinker to verify:
php artisan tinker
>>> Mail::raw('Test email body', function ($msg) {
... $msg->to('[email protected]')->subject('Test');
... });
Django
In your settings.py (or a settings/development.py if you split settings by environment):
# For local Mailpit
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'localhost'
EMAIL_PORT = 1025
EMAIL_USE_TLS = False
EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = ''
# For SendPit
EMAIL_HOST = 'smtp.sendpit.com'
EMAIL_PORT = 1025
EMAIL_HOST_USER = 'mb_your_credential'
EMAIL_HOST_PASSWORD = 'your_password'
Verify from the Django shell:
python manage.py shell
>>> from django.core.mail import send_mail
>>> send_mail('Test', 'Body text', '[email protected]', ['[email protected]'])
Ruby on Rails
In config/environments/development.rb:
# For local Mailpit
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: 'localhost',
port: 1025
}
# For SendPit
config.action_mailer.smtp_settings = {
address: 'smtp.sendpit.com',
port: 1025,
user_name: 'mb_your_credential',
password: 'your_password',
authentication: :plain
}
Test from the Rails console:
rails console
> ActionMailer::Base.mail(from: '[email protected]', to: '[email protected]', subject: 'Test', body: 'Hello').deliver_now
Node.js (Nodemailer)
Nodemailer is the standard Node.js library for sending email:
const nodemailer = require('nodemailer');
// For local Mailpit
const transporter = nodemailer.createTransport({
host: 'localhost',
port: 1025,
secure: false,
tls: {
rejectUnauthorized: false
}
});
// For SendPit
const transporter = nodemailer.createTransport({
host: 'smtp.sendpit.com',
port: 1025,
auth: {
user: 'mb_your_credential',
pass: 'your_password'
}
});
// Send a test email
await transporter.sendMail({
from: '[email protected]',
to: '[email protected]',
subject: 'Test Email',
text: 'Plain text body',
html: '<h1>HTML body</h1>'
});
Spring Boot (Java)
In application.properties (or application-dev.properties):
spring.mail.host=localhost
spring.mail.port=1025
spring.mail.username=
spring.mail.password=
spring.mail.properties.mail.smtp.auth=false
spring.mail.properties.mail.smtp.starttls.enable=false
Local vs Cloud: When to Use Which
This is not an either/or decision. Many teams use both.
Use a local fake SMTP server when:
- You are working offline or in an environment without internet access.
- You are debugging SMTP connection issues and want to eliminate network variables.
- You want maximum speed -- local SMTP capture is essentially instantaneous.
- You are the only person who needs to see the captured emails.
Use a cloud-hosted SMTP sandbox when:
- Multiple team members need to inspect the same test emails.
- You have a staging or shared development environment that multiple people use.
- Your CI/CD pipeline needs to send and verify emails.
- You do not want to maintain SMTP infrastructure across multiple environments.
- You need persistent email storage that survives container restarts and local machine rebuilds.
A practical hybrid approach: Use Mailpit locally for rapid development iteration. Use SendPit for your staging environment and CI pipeline, where team visibility and API access matter.
Security Considerations
Even though fake SMTP servers do not deliver emails to real recipients, there are security implications to consider.
Do not expose your fake SMTP server to the internet. If you run Mailpit on a cloud VM, bind it to 127.0.0.1 or restrict access with firewall rules. An open SMTP server on the internet will be found and abused within hours, even on non-standard ports.
# Bind Mailpit to localhost only
mailpit --smtp-bind-addr 127.0.0.1:1025 --ui-bind-addr 127.0.0.1:8025
Use authentication in shared environments. If your fake SMTP server is accessible to more than just your machine, enable authentication. Both Mailpit and SendPit support SMTP authentication. Cloud-hosted solutions like SendPit handle this for you with per-mailbox encrypted credentials.
Be careful with production data. If your staging environment uses a copy of production data, test emails might contain real customer information. Ensure your data anonymization process covers email addresses and any PII that appears in email templates.
Rotate credentials. Treat SMTP sandbox credentials like any other secret. If a team member leaves, rotate the mailbox credentials. With SendPit, you can regenerate credentials per mailbox without affecting other mailboxes.
Environment isolation. Never share SMTP sandbox credentials between environments. Your local dev, staging, and CI environments should each have their own mailbox or SMTP endpoint. This prevents test pollution and makes it easier to trace where an email came from.
Verifying Your Setup
After configuring your fake SMTP server, verify the full chain works. The simplest test is sending an email from your application and confirming it appears in the sandbox.
If emails are not appearing, check these common issues:
- Wrong port. SMTP sandboxes typically use port 1025 or 2525, not the standard 25 or 587. Verify your application is connecting to the right port.
- Docker networking. If your app and SMTP server are both in Docker, use the service name (e.g.,
mailpit) as the host, notlocalhost. Containers do not share a network namespace by default. - Firewall or security groups. On cloud VMs, ensure the SMTP port is open in your security group or firewall rules.
- TLS mismatch. If your application is configured to require TLS but the fake SMTP server does not support it (or vice versa), the connection will fail. Set
MAIL_ENCRYPTION=nullfor most development scenarios.
A quick diagnostic with telnet or nc can confirm basic SMTP connectivity:
# Test that the SMTP port is reachable
nc -zv localhost 1025
# Or manually speak SMTP
telnet localhost 1025
EHLO test
MAIL FROM:<[email protected]>
RCPT TO:<[email protected]>
DATA
Subject: Test
This is a test.
.
QUIT
If the SMTP server responds with 220 on connect and 250 to your commands, the server is working. If your application's emails still do not appear, the issue is in your application's mail configuration, not the SMTP server.
Conclusion
Setting up a fake SMTP server takes five minutes and saves you from a category of bugs and incidents that are genuinely painful -- accidental emails to real users, leaked test data, and the slow feedback loop of testing against production mail services.
For solo development, Mailpit in Docker is hard to beat. For teams, a cloud-hosted sandbox like SendPit removes infrastructure overhead and gives everyone visibility into test emails. For a quick sanity check, Python's built-in SMTP server does the job with zero setup.
Pick the approach that fits your workflow, configure your framework, and never worry about test emails reaching real inboxes again.
Nikhil Rao
Creator of SendPit. Building developer tools for email testing and SMTP infrastructure.
About SendPit →