aiosmtpd - Asyncio-based SMTP Server
raw JSON → 1.4.6 verified Fri May 15 auth: no python
aiosmtpd is an asyncio-based SMTP and LMTP server, providing an asynchronous, RFC 5321 compliant server that supports customizable extensions. It serves as a modern replacement for the deprecated `smtpd` module in the Python standard library. The current version is 1.4.6, and it's actively maintained under the aio-libs umbrella project.
pip install aiosmtpd Common errors
error ModuleNotFoundError: No module named 'aiosmtpd' ↓
cause The 'aiosmtpd' package is not installed in the Python environment.
fix
Install the package using pip: 'pip install aiosmtpd'.
error ModuleNotFoundError: No module named 'public' ↓
cause An outdated version of 'setuptools' is being used, which lacks support for the 'public' module required by 'aiosmtpd'.
fix
Upgrade 'setuptools' to at least version 46.4.0: 'pip install --upgrade setuptools'.
error ImportError: No module named 'aiosmtpd' ↓
cause The 'aiosmtpd' package is not installed in the Python environment.
fix
Install the package using pip: 'pip install aiosmtpd'.
error OSError: [Errno 98] Address already in use ↓
cause This error occurs when the `aiosmtpd` server attempts to bind to a network address (IP and port) that is already being used by another process on your system.
fix
Ensure no other process is listening on the desired port. You can either choose a different port for your
aiosmtpd server or terminate the process currently occupying the port. For development, you can often add sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) (though aiosmtpd's Controller usually handles this; checking for an actively running process is the primary solution). error Connection refused ↓
cause This commonly happens when an `aiosmtpd` server is configured to listen only on localhost (e.g., '::1' or '127.0.0.1') by default, but a client attempts to connect from a different IP address, or when the server is not running or a firewall is blocking the connection.
fix
When initializing the
Controller, specify hostname='0.0.0.0' to listen on all available network interfaces, allowing connections from other machines or Docker containers. Also, verify the server is running and that no firewall is blocking the port. Warnings
breaking The signature of the `handle_NOOP()` method in custom handlers changed from taking zero arguments to requiring a single argument in version 1.4.x. Custom handlers implementing this method must be updated. ↓
fix Update `handle_NOOP(self, server)` to accept the `server` argument: `async def handle_NOOP(self, server): ...`
gotcha By default, the SMTP AUTH extension might not be advertised or supported by the server unless a STARTTLS command is issued first, or `auth_require_tls=False` is explicitly passed to the `Controller` or `SMTP` constructor. This can cause authentication failures with clients expecting immediate AUTH support. ↓
fix If clients connect without STARTTLS and require AUTH, initialize `Controller` or `SMTP` with `auth_require_tls=False`: `Controller(handler, ..., auth_require_tls=False)`.
deprecated The `process_message()` method in handler classes was deprecated. Implementations should now use the asynchronous `handle_DATA(self, server, session, envelope)` method for processing incoming mail data. ↓
fix Migrate `process_message()` logic to `async def handle_DATA(self, server, session, envelope): ...` ensuring it returns an SMTP response string (e.g., `'250 OK'`).
deprecated The `authentication_handler` parameter for the `SMTP` class constructor was deprecated in favor of `authenticator` and is scheduled for removal in version 2.0. ↓
fix Replace `authentication_handler=my_func` with `authenticator=Authenticator(my_func)` (where `Authenticator` is imported from `aiosmtpd.smtp`).
gotcha The default `ready_timeout` for `Controller.start()` to wait for the SMTP server thread to become ready changed from 1 second to 5 seconds. This could impact testing setups or deployments sensitive to startup times. ↓
fix Adjust tests or application logic that rely on a specific short startup timeout, or explicitly set `ready_timeout` in the `Controller` constructor: `Controller(handler, ..., ready_timeout=1.0)`.
breaking While PyPI states `requires_python >=3.8`, the official documentation recommends CPython>=3.9 and PyPy>=3.9. The upcoming version 1.4.7 explicitly drops support for Python 3.8, requiring an upgrade if using newer `aiosmtpd` versions. ↓
fix Ensure your environment uses Python 3.9 or newer to guarantee compatibility with current and future `aiosmtpd` releases.
Install compatibility last tested: 2026-05-15 v1.4.6 (up to date)
python os / libc status wheel install import disk mem side effects
3.10 alpine (musl) wheel - 0.24s 19.4M 11.1M clean
3.10 slim (glibc) wheel 1.6s 0.14s 20M 11.1M clean
3.11 alpine (musl) wheel - 0.32s 21.6M 12.4M clean
3.11 slim (glibc) wheel 1.8s 0.28s 22M 12.4M clean
3.12 alpine (musl) wheel - 0.51s 13.4M 12.7M clean
3.12 slim (glibc) wheel 1.6s 0.49s 14M 12.7M clean
3.13 alpine (musl) wheel - 0.50s 13.1M 13.0M clean
3.13 slim (glibc) wheel 1.7s 0.46s 14M 13.0M clean
3.9 alpine (musl) wheel - 0.19s 18.9M 10.8M clean
3.9 slim (glibc) wheel 1.9s 0.16s 19M 10.8M clean
Imports
- Controller wrong
from aiosmtpd import Controllercorrectfrom aiosmtpd.controller import Controller - SMTP
from aiosmtpd.smtp import SMTP - handlers.Sink
from aiosmtpd.handlers import Sink - handlers.Debugging
from aiosmtpd.handlers import Debugging
Quickstart
import asyncio
from aiosmtpd.controller import Controller
from aiosmtpd.handlers import Debugging
import os
async def amain():
# Use Debugging handler to print incoming emails to console
handler = Debugging()
# The Controller runs the SMTP server in a separate thread.
# For testing/quickstart, localhost:8025 is common.
controller = Controller(handler, hostname=os.environ.get('SMTP_HOST', '127.0.0.1'), port=int(os.environ.get('SMTP_PORT', 8025)))
print(f"Starting SMTP server on {controller.hostname}:{controller.port}...")
controller.start()
print("SMTP server started. Press Ctrl+C to stop.")
try:
# Keep the main loop running while the controller's thread handles the SMTP server
await asyncio.Event().wait()
except asyncio.CancelledError:
pass
finally:
controller.stop()
print("SMTP server stopped.")
if __name__ == '__main__':
# Run the asyncio event loop
try:
asyncio.run(amain())
except KeyboardInterrupt:
print("Server interrupted by user.")