SMTP Testing: Test Outbound Emails Without Sending to Real Inboxes
Compare SMTP testing approaches — fake SMTP servers, email sandboxes, and real on-demand inboxes — to find the right fit for your development workflow. Includes framework configs and cost analysis.

Every application that sends email needs a way to test that email without sending it to real users. A misconfigured staging environment that sends 10,000 test notifications to actual customers is not a hypothetical — it is a rite of passage for enough teams that the industry has built multiple categories of tools to prevent it. The question is not whether you need SMTP testing. It is which approach matches your situation, your budget, and the level of confidence your team requires.
SMTP, the Simple Mail Transfer Protocol defined in RFC 5321, is the standard for sending email between servers. Testing SMTP means verifying that your application correctly composes, authenticates, and transmits email messages. But "correctly" means different things depending on what you are testing for — template rendering, delivery reliability, authentication configuration, or end-to-end flows that include reading the email back.
This guide covers all three major approaches to SMTP testing, with framework-specific configurations, cost comparisons, and guidance on when to use each one.
Why SMTP Testing Matters
Outbound email goes wrong in predictable ways, and every one of these failure modes has caused real production incidents at companies of every size:
- Staging sends to production addresses. A developer forgets to update the recipient list, and test data hits real inboxes. This happens more often than anyone admits, and the damage ranges from embarrassing to legally consequential.
- Templates render incorrectly. HTML email is its own discipline. What looks right in a browser can break in Outlook, Gmail, or Apple Mail. The CSS support across email clients varies dramatically — Outlook still uses Word's rendering engine for HTML emails.
- Transactional emails contain wrong data. Password reset links point to localhost. Invoice amounts show test values. Verification codes are hardcoded strings. These bugs are invisible without actually reading the sent email.
- Email does not send at all. SMTP configuration errors fail silently in many frameworks. Your application might be swallowing connection failures without logging them, leaving you with no indication that emails have stopped going out.
- Authentication fails in production but not locally. SPF, DKIM, and DMARC configuration issues do not surface when sending to a sandbox. They only appear when real mail servers evaluate your messages against DNS records.
Testing catches all of this before it reaches users. The three main approaches each address different parts of the problem, and many teams use more than one.
Approach 1: Fake SMTP Servers
A fake SMTP server accepts email over SMTP but does not deliver it anywhere. It stores messages locally and provides a UI to inspect them. This is the most basic form of SMTP testing.
Mailhog
Mailhog is the most widely used open-source fake SMTP server. You run it locally (usually via Docker), point your application's SMTP config at it, and all outbound email gets captured in Mailhog's web UI.
# docker-compose.yml
services:
mailhog:
image: mailhog/mailhog
ports:
- "1025:1025" # SMTP
- "8025:8025" # Web UI
Configure your app to send to localhost:1025, and every email appears at http://localhost:8025.
Pros: Free, open source, zero external dependencies, great for local development.
Cons: Self-hosted (requires Docker), no real delivery, not easily available in CI environments, no IMAP access, maintenance has stalled. See our Mailhog alternatives guide for more options.
smtp4dev
Similar to Mailhog but built on .NET. Offers a slightly more polished UI and is actively maintained. Same concept — fake SMTP, local capture, no delivery.
Best for: .NET-heavy teams who want a local SMTP capture tool that integrates with their existing stack.
Mailpit
Mailpit is a modern successor to Mailhog, written in Go and actively maintained. It offers a cleaner UI, better search, and an API for automated checking. If you are currently using Mailhog, Mailpit is a drop-in replacement worth evaluating.
# docker-compose.yml
services:
mailpit:
image: axllent/mailpit
ports:
- "1025:1025" # SMTP
- "8025:8025" # Web UI
When Fake SMTP Is the Right Tool
Use a fake SMTP server when:
- You are developing locally and want to see what emails your app sends
- You need to inspect HTML templates visually
- You do not need to test actual delivery or IMAP retrieval
- Your team is comfortable with Docker and self-hosting
- You want zero cost and zero external dependencies
The fundamental limitation is that fake SMTP tests sending, not delivery. Your email never leaves your machine. If your production issue is about deliverability, spam filtering, or IMAP retrieval, fake SMTP will not catch it.
Approach 2: Email Sandboxes
An email sandbox is a hosted service that captures outbound email, similar to a fake SMTP server but without the self-hosting overhead. Mailtrap is the most well-known player in this space.
How Sandboxes Work
You configure your application to send email through the sandbox's SMTP server. The sandbox captures every message, displays it in a web dashboard, and provides tools for inspecting headers, HTML rendering, spam scores, and more.
No email actually gets delivered. The sandbox is a dead end by design — it is there to prevent exactly the kind of accidental sends that SMTP testing exists to stop.
Mailtrap
Mailtrap provides project-based inboxes, team collaboration features, HTML rendering previews, and an API for automated checks. Free tier has limits; paid plans run $15-35/month.
Pros: Hosted (no Docker), good UI, team features, HTML preview, spam analysis.
Cons: Subscription pricing, no real delivery in sandbox mode, no IMAP access, overkill for simple "does this email arrive?" testing. See Mailtrap alternatives for a full comparison.
Ethereal Email
Built by the Nodemailer team, Ethereal provides free fake SMTP accounts. You create disposable credentials, send email to them, and view messages in the Ethereal web UI.
Pros: Free, no signup needed, perfect for quick tests.
Cons: No persistence (messages expire quickly), no team features, no API, not suitable for CI/CD.
When Sandboxes Are the Right Tool
Use an email sandbox when:
- Your team needs a shared view of test emails
- You want HTML rendering previews and spam score checking
- You do not want to self-host anything
- You do not need to test real email delivery or IMAP retrieval
- You are willing to pay a monthly subscription for convenience
Sandboxes occupy a middle ground between free local tools and full-featured real inboxes. They add convenience over fake SMTP but do not close the gap on delivery testing.
Approach 3: Real On-Demand Inboxes
The third approach is using real email inboxes with real SMTP and IMAP credentials. You send email through real SMTP, it gets delivered to a real inbox, and you read it back via real IMAP. End-to-end.
Reusable.Email Managed Inboxes
A managed inbox on Reusable.Email is a full email account. It has SMTP credentials for sending (smtp.reusable.email:587, STARTTLS) and IMAP credentials for reading (imap.reusable.email:993, SSL/TLS).
The key difference from sandboxes: email is actually delivered. Your tests verify the entire email pipeline, not just the sending half. You can confirm that emails pass spam filters, that IMAP clients can parse them correctly, that multipart MIME messages render as expected, and that headers like SPF and DKIM are properly configured.
Cost: $3 per inbox, one-time. No subscription. No renewal.
Quick Setup Example
Configure your application to send through a managed inbox's SMTP credentials:
# Django settings.py
EMAIL_HOST = "smtp.reusable.email"
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = "[email protected]"
EMAIL_HOST_PASSWORD = "your-inbox-password"
// Node.js with nodemailer
const transporter = nodemailer.createTransport({
host: "smtp.reusable.email",
port: 587,
secure: false, // STARTTLS
auth: {
user: "[email protected]",
pass: "your-inbox-password",
},
});
Now your staging or test environment sends real email through Reusable.Email's SMTP server. You can read those emails via IMAP in your test assertions, or just check them in any email client.
When Real Inboxes Are the Right Tool
Use real on-demand inboxes when:
- You need to test end-to-end email delivery, not just sending
- Your tests need to read emails via IMAP (parse verification codes, check links)
- You want staging email that is isolated but real
- You need SMTP credentials for integration with external services
- You want a one-time cost instead of a monthly subscription
- You need to verify SPF, DKIM, and DMARC alignment
Choosing the Right Approach
| Need | Fake SMTP | Sandbox | Real Inbox |
|---|---|---|---|
| Local dev email preview | Yes | Yes | Overkill |
| HTML template inspection | Basic | Best | Via client |
| CI/CD email assertions | Difficult | Via API | Via IMAP |
| End-to-end delivery test | No | No | Yes |
| IMAP/POP3 retrieval test | No | No | Yes |
| SPF/DKIM/DMARC verification | No | Partial | Yes |
| Team collaboration | No | Yes | Shared creds |
| Zero ops | No (Docker) | Yes | Yes |
| Cost | Free | $15-35/mo | $3 once |
Many teams use more than one approach. Mailhog or Mailpit for local development, real inboxes for CI and staging. The approaches are not mutually exclusive — they complement each other at different stages of the development lifecycle.
Cost Comparison Over 12 Months
| Approach | Setup Cost | Monthly Cost | 12-Month Total | Real Delivery |
|---|---|---|---|---|
| Mailhog/Mailpit (self-hosted) | Free | $0 + ops time | $0 + ops time | No |
| Mailtrap (Team plan) | $0 | $25/month | $300 | No |
| Mailosaur | $0 | $35/month | $420 | Partial |
| Reusable.Email (10 inboxes) | $30 | $0 | $30 | Yes |
| Reusable.Email Whitelabel | $0 | $30/month | $360 | Yes |
For teams that need 1-50 managed inboxes, the one-time pricing model is dramatically cheaper than any subscription service. The whitelabel tier makes sense when you need unlimited inboxes or the REST API for programmatic inbox creation.
Common SMTP Testing Mistakes
These are the mistakes that cause the most wasted debugging time. Avoiding them saves hours per month across a development team.
Hardcoding production SMTP credentials in test configs. If your test suite accidentally uses production SendGrid or SES credentials, test emails go to real addresses. Use environment-specific configuration files and never share credentials between environments. Store SMTP passwords in your CI platform's secrets manager, not in configuration files committed to version control.
Not testing the full loop. Verifying that sendmail() did not throw an exception is not an email test. The email might still be malformed, missing headers, or caught by spam filters. Read it back via IMAP to verify the actual content. For a complete guide to building this kind of end-to-end email test, see How to Build an Email Testing Environment with a Disposable Email API.
Ignoring encoding issues. HTML emails with special characters, Unicode names, or non-ASCII subjects fail in specific email clients. Test with realistic data, not just ASCII strings. The MIME specification (RFC 2047) defines how non-ASCII characters should be encoded in email headers, but not all sending libraries handle this correctly by default.
Sharing a single inbox across parallel test runs. If two CI jobs send to the same test inbox simultaneously, their emails interleave and tests get flaky. Use one inbox per test suite, or filter on unique message IDs. With managed inboxes at $3 each, the cost of isolation is negligible.
Testing only the happy path. Also test what happens when SMTP credentials are wrong, when the server is unreachable, and when the recipient address is invalid. Your application should handle these failures gracefully, logging the error and potentially retrying rather than silently dropping messages.
Not testing email headers. Headers like Reply-To, List-Unsubscribe, X-Mailer, and authentication results (SPF, DKIM) affect both deliverability and user experience. A missing List-Unsubscribe header can cause Gmail to classify your transactional email as spam.
Setting Up SMTP Testing in Common Frameworks
Most web frameworks have a configuration file or environment variable for SMTP settings. Here is how to point common frameworks at a managed inbox for staging and testing.
Django (settings/staging.py):
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "smtp.reusable.email"
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = os.environ.get("SMTP_USER", "[email protected]")
EMAIL_HOST_PASSWORD = os.environ.get("SMTP_PASSWORD")
DEFAULT_FROM_EMAIL = os.environ.get("SMTP_USER", "[email protected]")
Rails (config/environments/staging.rb):
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: "smtp.reusable.email",
port: 587,
user_name: "[email protected]",
password: ENV["SMTP_PASSWORD"],
authentication: :plain,
enable_starttls_auto: true,
}
Laravel (.env):
MAIL_MAILER=smtp
MAIL_HOST=smtp.reusable.email
MAIL_PORT=587
[email protected]
MAIL_PASSWORD=your-inbox-password
MAIL_ENCRYPTION=tls
[email protected]
MAIL_FROM_NAME="${APP_NAME}"
Spring Boot (application-staging.properties):
spring.mail.host=smtp.reusable.email
spring.mail.port=587
[email protected]
spring.mail.password=${SMTP_PASSWORD}
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
Next.js / Node.js (environment-based):
// lib/email.js
const nodemailer = require("nodemailer");
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST || "smtp.reusable.email",
port: parseInt(process.env.SMTP_PORT || "587"),
secure: false,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
});
module.exports = { transporter };
Go (using net/smtp):
package main
import (
"net/smtp"
"os"
)
func sendEmail(to, subject, body string) error {
auth := smtp.PlainAuth(
"",
os.Getenv("SMTP_USER"),
os.Getenv("SMTP_PASS"),
"smtp.reusable.email",
)
msg := []byte("Subject: " + subject + "\r\n" +
"From: " + os.Getenv("SMTP_USER") + "\r\n" +
"To: " + to + "\r\n" +
"\r\n" + body)
return smtp.SendMail(
"smtp.reusable.email:587",
auth,
os.Getenv("SMTP_USER"),
[]string{to},
msg,
)
}
The pattern is the same in every case: point the SMTP host at smtp.reusable.email, port 587, STARTTLS enabled, and provide the managed inbox credentials. Use environment variables for the password so the same codebase works across development, staging, and CI environments.
SMTP Testing in CI/CD Pipelines
Setting up SMTP testing in CI requires a few extra considerations beyond local development. The CI environment needs network access to the SMTP and IMAP servers, credentials stored securely, and test code that handles the asynchronous nature of email delivery.
GitHub Actions Example
name: Email Integration Tests
on: [push, pull_request]
jobs:
email-tests:
runs-on: ubuntu-latest
env:
SMTP_USER: ${{ secrets.SMTP_USER }}
SMTP_PASS: ${{ secrets.SMTP_PASS }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install -r requirements.txt
- run: pytest tests/email/ -v
Key CI Considerations
- Network access: Ensure your CI provider allows outbound connections on ports 587 (SMTP) and 993 (IMAP). Most cloud CI providers do by default.
- Credential management: Use your CI platform's secret management. Never commit SMTP passwords to version control.
- Test timeouts: Email delivery takes seconds, not milliseconds. Set test timeouts to at least 30 seconds per email operation.
- Inbox cleanup: Mark all messages as seen at the start of each test run to avoid assertions against stale emails from previous runs.
What's Next
For a deeper dive into building automated email tests with complete Python and Node.js examples, see How to Build an Email Testing Environment with a Disposable Email API. For the full developer overview of Reusable.Email's capabilities including webhooks and per-user inboxes, start with the Email API for Developers guide.
If your team needs to receive and process inbound email programmatically, rather than just testing outbound flows, read the receive email API guide for IMAP polling and webhook implementation patterns. And for teams evaluating disposable email workflows for QA and staging, that guide covers the full spectrum of developer-focused email tools.
FAQ
Should I use a fake SMTP server or a real inbox for CI testing?
It depends on what you are testing. If you only need to verify that your application correctly calls the SMTP library with the right parameters, a fake SMTP server or mock is sufficient and faster. If you need to verify that the email actually arrives, contains the right content when parsed by an email client, and passes authentication checks, you need a real inbox. Most mature testing strategies use both — fast unit tests with mocks and slower integration tests with real delivery.
How do I prevent staging emails from reaching real users?
Configure your staging environment's SMTP credentials to point at a managed inbox on Reusable.Email rather than your production email service. All outbound email from staging goes to the managed inbox, where you can inspect it. Because the SMTP credentials are scoped to that specific inbox, there is no path for email to reach production addresses even if your application code contains real user email addresses.
Can I test email rendering across different clients without sending to real accounts?
Partially. You can send an email to a managed inbox and open it in Thunderbird, Apple Mail, or Outlook to see how it renders in those specific clients. For comprehensive cross-client testing (testing against dozens of email clients simultaneously), dedicated services like Litmus or Email on Acid are purpose-built for that use case. Real inboxes and rendering tools serve complementary roles.
What SMTP port should I use — 25, 465, or 587?
Port 587 with STARTTLS is the recommended port for email submission, as defined in RFC 6409. Port 25 is for server-to-server relay and is often blocked by ISPs and cloud providers. Port 465 was briefly designated for SMTP over implicit TLS, deprecated, and then re-assigned in RFC 8314. Reusable.Email uses port 587 with STARTTLS, which is the most widely supported and recommended configuration.
How do I test that my emails pass spam filter checks?
Send a test email to a real managed inbox and examine the email headers. Look for Authentication-Results, Received-SPF, and DKIM-Signature headers. These indicate whether your email passed SPF, DKIM, and DMARC checks. Real inboxes evaluate these authentication mechanisms, while sandboxes typically skip them entirely.
Try it free
Get a disposable inbox in seconds
No sign-up required. Just visit an address and it's live. Works with any domain on reusable.email.
Open your inbox →

