How to Send an Email with smtplib and a Mock Server in Python
Send an email using smtplib and verify it with a local aiosmtpd mock SMTP server — perfect for testing without a real mail server.
pip install aiosmtpd
Python code
37 linesimport smtplib
from email.message import EmailMessage
import aiosmtpd.controller as controller
import threading
def handle_message(server, session, envelope):
print(f"Mock server received message:")
print(f"From: {envelope.mail_from}")
print(f"To: {envelope.rcpt_tos}")
print(f"Subject: {envelope.content.decode()}")
return '250 OK'
def main():
# Start mock SMTP server on port 1025
mock_server = controller.Controller(handler=handle_message, hostname='127.0.0.1', port=1025)
mock_server.start()
print("Mock SMTP server started on 127.0.0.1:1025")
# Create email message
msg = EmailMessage()
msg.set_content("Hello, this is a test email sent with smtplib!")
msg['Subject'] = "Test Email"
msg['From'] = "sender@example.com"
msg['To'] = "recipient@example.com"
# Send email via mock server
with smtplib.SMTP('127.0.0.1', 1025) as client:
client.send_message(msg)
print("Email sent successfully via mock server")
mock_server.stop()
if __name__ == "__main__":
main()
Output
Mock SMTP server started on 127.0.0.1:1025
Mock server received message:
From: sender@example.com
To: recipient@example.com
Subject: Test Email
Email sent successfully via mock server
How it works
aiosmtpd runs a lightweight SMTP server in-process, letting you test email sending without a real mail service. The handle_message callback receives the envelope with sender, recipients, and raw content — here we print them to prove delivery. smtplib.SMTP connects to that local server and send_message handles RFC-compliant formatting automatically. The with block guarantees the connection closes, and EmailMessage gives a clean API for subject, from, and to headers. Stopping the controller shuts down the mock server gracefully after the test.
Common mistakes
- Forgetting to start the controller before sending — SMTP connection will fail with a connection refused error
- Not calling `mock_server.stop()` which leaves a zombie port open
- Confusing `send_message` (accepts EmailMessage) with `sendmail` (needs raw strings)
Variations
- Use `smtplib.SMTP_SSL` instead of `SMTP` for real Gmail or Outlook connections over port 465
- Replace the mock server with `smtplib.SMTP('smtp.gmail.com', 587).starttls()` for production email delivery
Real-world use cases
- Writing unit tests for an email notification feature without spamming real addresses.
- Validating email formatting and SMTP logic in a CI/CD pipeline in isolation.
- Developing a mail-sending script locally where no corporate SMTP relay is accessible.
Sponsored
More from Automation & scripting
- Aggregate Log Errors Count by Hour in Python easy
- Automate Tweeting New Blog Posts in Python easy
- Automatically Clean Temporary Files from Applications Using Python medium
- Automatically Download the Latest Software Release from GitHub with Python medium
- Automatically Generate Charts from CSV Files with One Command medium
- Automatically Generate Hardware Inventory Reports in Python easy
Keep learning
Related tutorials and quizzes for this topic.