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


2 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