How to Set Up a Local Dummy SMTP Server for Email Testing and Debugging


17 views

When debugging applications that send emails, you often need to verify email sending behavior without actually delivering messages. Common scenarios include:

  • Testing email sending frequency (like your case of 7x over-sending)
  • Validating email content formatting
  • Checking recipient addressing
  • Performance testing under heavy email loads

Python's standard library includes smtpd which is perfect for this:

import smtpd
import asyncore
from datetime import datetime

class CustomSMTPServer(smtpd.SMTPServer):
    def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
        timestamp = datetime.now().strftime("[%Y-%m-%d %H:%M:%S]")
        with open("mail.log", "a") as log:
            log.write(f"{timestamp} sent mail to {', '.join(rcpttos)}\n")
        return

server = CustomSMTPServer(('localhost', 1025), None)
asyncore.loop()

For those who prefer pre-built solutions:

  • MailCatcher: Ruby-based with web interface (runs on port 1080)
  • FakeSMTP: Java-based with GUI (standalone JAR file)
  • MailHog: Written in Go with API and web UI

Configure your app to use the dummy server:

# For Python applications using smtplib
import smtplib
smtp = smtplib.SMTP('localhost', 1025)
smtp.sendmail(
    'from@example.com',
    ['to@example.com'],
    'Subject: Test\n\nBody content'
)

Enhance the basic logger to capture more details:

class EnhancedSMTPServer(smtpd.SMTPServer):
    def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
        timestamp = datetime.now().isoformat()
        log_entry = {
            "timestamp": timestamp,
            "sender": mailfrom,
            "recipients": rcpttos,
            "size": len(data),
            "client": f"{peer[0]}:{peer[1]}"
        }
        with open("mail.jsonl", "a") as log:
            log.write(json.dumps(log_entry) + "\n")

For containerized environments:

# docker-compose.yml
version: '3'
services:
  smtp:
    image: mailhog/mailhog
    ports:
      - "1025:1025"
      - "8025:8025"

When debugging email-sending functionality in applications, you often encounter situations where emails are being sent unexpectedly or in incorrect quantities. A local SMTP server that captures but doesn't actually send emails is invaluable for testing.

Several open-source projects provide this functionality:

  • MailCatcher: Ruby-based, provides web interface
  • FakeSMTP: Java-based, GUI application
  • MailHog: Go-based, API and web interface

For those who prefer a lightweight solution, here's a minimal Python SMTP server that logs to console:

import smtpd
import asyncore
from datetime import datetime

class CustomSMTPServer(smtpd.SMTPServer):
    def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
        timestamp = datetime.now().strftime("[%Y-%m-%d %H:%M:%S]")
        print(f"{timestamp} Mail from: {mailfrom}, To: {rcpttos}")
        return None

server = CustomSMTPServer(('localhost', 1025), None)
asyncore.loop()

Save the code to a file (e.g., fake_smtp.py) and run:

python fake_smtp.py

Point your application's SMTP settings to:

  • Host: localhost
  • Port: 1025
  • Authentication: None required

To log to a file instead of console, modify the process_message method:

def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
    timestamp = datetime.now().strftime("[%Y-%m-%d %H:%M:%S]")
    with open("mail.log", "a") as f:
        f.write(f"{timestamp} Mail from: {mailfrom}, To: {', '.join(rcpttos)}\n")
    return None

For containerized environments, MailHog provides a ready-to-use solution:

docker run -d -p 1025:1025 -p 8025:8025 mailhog/mailhog