aiosmtpd

An asyncio-based SMTP and LMTP server implementation for Python, built to mock mail servers in tests and run lightweight custom relays.

Library
PyPI
v1.4.6
373stars
Apache License 2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
71/100Good
Development Activity72
Maintenance52
Community80
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
79/100Good
Architecture85
Code Quality92
Innovation65
Learning Curve75

aiosmtpd reimplements Python’s deprecated stdlib smtpd.py on top of asyncio, giving developers a modern, coroutine-friendly SMTP and LMTP server they can embed directly in an application or a test suite. At its core is a Controller class that spins up an event loop (or attaches to a running one) and a SMTP protocol class that implements the RFC 5321/6152/3207 command set, including STARTTLS, AUTH, and pluggable size limits, alongside pipelining and SMTPUTF8 support.

Rather than baking in a single delivery mechanism, aiosmtpd separates protocol handling from message processing through a Handler interface: implementations receive hook callbacks (handle_RCPT, handle_DATA, handle_AUTH, etc.) and decide what happens to a message, whether that’s queuing to a Mailbox, proxying to a real MTA, or simply asserting on the envelope in a test. This makes it the standard choice in the Python ecosystem for verifying that application code sends the mail it claims to, without touching a real mail server, while remaining capable enough to run a small production-grade relay or LMTP endpoint.

What You Get

  • A Controller class that manages the asyncio event loop and socket lifecycle for you — start/stop a server in a background thread with one call
  • A full SMTP protocol implementation covering HELO/EHLO, MAIL/RCPT/DATA, STARTTLS, AUTH (PLAIN/LOGIN), pipelining, and SMTPUTF8
  • An LMTP server variant (aiosmtpd.lmtp.LMTP) for talking to local mail delivery agents
  • Built-in Handler implementations (Debugging, Mailbox, AsyncMessage) plus a documented hook protocol for writing custom handlers
  • A main.py/aiosmtpd console-script CLI for running a standalone server from the command line
  • PROXY protocol (v1/v2) support for accepting connections behind a TCP load balancer

Common Use Cases

  • Spinning up a throwaway SMTP server inside pytest/unittest suites to assert on the exact envelope and message an application sends
  • Building a custom mail relay or filtering MTA that inspects, rewrites, or rejects messages via a custom Handler
  • Running a local development mail catcher so outgoing mail from an app never reaches real inboxes
  • Implementing an LMTP endpoint for local mail delivery pipelines
  • Prototyping SMTP authentication or STARTTLS flows without standing up Postfix/Exim

Under The Hood

Architecture aiosmtpd is organized as a thin, well-separated stack: controller.py owns process/event-loop lifecycle (starting the loop in a background thread, binding TCP/Unix sockets, waiting for readiness with a configurable timeout), smtp.py implements the actual asyncio.StreamReaderProtocol subclass that drives the SMTP state machine command-by-command (smtp_HELO, smtp_MAIL, smtp_DATA, etc.), and handlers.py defines the Handler hook contract plus several ready-made handlers (Debugging, Mailbox, AsyncMessage). lmtp.py extends the same protocol class for LMTP with minimal overrides, and proxy_protocol.py adds an optional decoding layer for PROXY-protocol-wrapped connections in front of a load balancer. This separation means swapping in a custom Handler changes only message disposition, never protocol handling, and swapping Controller subclasses (UnixSocketController, UnthreadedController) changes only how the loop is hosted.

Tech Stack The library targets CPython and PyPy 3.9+ and depends on very little beyond the standard library — atpublic for explicit __all__ management via a @public decorator, and attrs for a couple of structured value types. The test suite pulls in pytest, pytest-cov, pytest-mock, coverage, and trustme (for generating throwaway TLS certificates to exercise STARTTLS/AUTH paths). Packaging uses classic setuptools/setup.cfg, and the project is exercised across Linux, Windows, MacOS, and Cygwin in CI via tox, with pytype for static type checking and flake8/bandit for style and security linting.

Code Quality The project enforces 100% branch coverage (fail_under = 100 in its coverage config) across an extensive aiosmtpd/tests/ suite (test_smtp.py, test_smtps.py, test_starttls.py, test_lmtp.py, test_proxyprotocol.py, test_handlers.py, and more), run via tox across every supported interpreter combination with coverage, diff-coverage, and profiling variants. Code carries type hints throughout (py.typed marker present) and is checked with pytype; style is enforced with flake8/flake8-bugbear, and bandit runs as a security linter in CI. Error handling is explicit and deliberate — protocol-level failures raise typed exceptions (TLSSetupException) or degrade to documented SMTP error responses rather than being silently swallowed.

What Makes It Unique aiosmtpd’s distinguishing choice is treating the SMTP/LMTP protocol implementation and message handling as fully decoupled concerns behind a small, well-documented Handler hook API, which is what makes it equally suitable as a production-grade relay component and as a disposable test fixture — a duality most SMTP testing tools don’t attempt. Its PROXY protocol support and explicit IPv6-preferring Controller design also reflect attention to real deployment topologies (load balancers, containers) rather than only the toy-server case, and its adoption of asyncio as a first-class rewrite of the deprecated stdlib smtpd module keeps it aligned with modern Python concurrency instead of the retired asyncore/asynchat APIs.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search