Skip to main content

SMTPDFix: Test email, locally

build

A simple SMTP server based on aiosmtpd for use as a fixture with pytest that supports encryption and authentication. All this does is receives messages and appends them to a list as an email.Message.

Not intended for use with production systems.

This fixture is intended to address cases where to test an application that sends an email, it needs to be intercepted for subsequent processing. For example, sending an email with a code for password reset or two-factor authentication. This fixture allows a test to trigger the email being sent, ensure that it's sent, and read the email.

Installing

To install using pip, first upgrade pip to the latest version to avoid any issues installing cryptography:

$ python -m pip install --upgrade pip
$ pip install smtpdfix

Or, if you're using setuptools, it can be included in the extras_require argument of a setup.py file:

setup(
    ...
    extras_require={
        "test": [
            "pytest",
            "smtpdfix",
        ],
    },
)

and then installed with pip (-e assumes that you want your project to be editable):

$ python -m pip install --upgrade pip
$ pip install -e .[test]

Using

The SMTPDFix plugin, smtpd, automatically registers for use with pytest when you install smtpdfix. To use it simply add to your test method.

from smtplib import SMTP


def test_sendmail(smtpd):
    from_addr = "from.addr@example.org"
    to_addrs = "to.addr@example.org"
    msg = (f"From: {from_addr}\r\n"
           f"To: {to_addrs}\r\n"
           f"Subject: Foo\r\n\r\n"
           f"Foo bar")

    with SMTP(smtpd.hostname, smtpd.port) as client:
        client.sendmail(from_addr, to_addrs, msg)

    assert len(smtpd.messages) == 1

To use STARTTLS:

from smtplib import SMTP


def test_sendmail(smtpd):
    smptd.config.use_starttls = True
    from_ = "from.addr@example.org"
    to_ = "to.addr@example.org"
    msg = (f"From: {from_}\r\n"
           f"To: {to_}\r\n"
           f"Subject: Foo\r\n\r\n"
           f"Foo bar")

    with SMTP(smtpd.hostname, smtpd.port) as client:
        client.starttls()  # Note that you need to call starttls first.
        client.sendmail(from_addr, to_addrs, msg)

    assert len(smtpd.messages) == 1

As of version 0.2.7 the plugin automatically registers and it is not necessary to include it manually by adding pytest_plugins = "smtpdfix" to the module or conftest.py.

The certificates included with the fixture will work for addresses localhost, localhost.localdomain, 127.0.0.1, 0.0.0.1, ::1. If using other addresses the key (key.pem) and certificate (cert.pem) must be in a location specified under SMTP_SSL_CERTS_PATH.

Not as a fixture

In some situations it may be desirable to not use the fixture which is initialized before entering the test. This can be accomplished by using the SMTPDFix class.

from smtplib import SMTP

from smtpdfix import SMTPDFix


def test_smtpdfix():
    hostname, port = "127.0.0.1", 8025

    with SMTPDFix(hostname, port) as smtpd:
        with SMTP(hostname, port) as client:
            from_addr = "foo@example.org"
            to_addrs = "bar@example.org"
            msg = (f"From: {from_addr}\r\n"
                   f"To: {to_addrs}\r\n"
                   f"Subject: Foo\r\n\r\n"
                   f"Foo bar")

            client.sendmail(from_addr, to_addrs, msg)

        assert len(smtpd.messages) == 1

Configuration

Configuration is handled through properties in the config of the fixture and are initially set from environment variables:

Property Variable Default Description
host SMTPD_HOST 127.0.0.1 or ::1 The hostname that the fixture will listen on.
port SMTPD_PORT a random free port The port that the fixture will listen on.
ready_timeout SMTPD_READY_TIMEOUT 10.0 The seconds the server will wait to start before raising a TimeoutError.
login_username SMTPD_LOGIN_NAME user Username for default authentication.
login_password SMTPD_LOGIN_PASSWORD password Password for default authentication.
use_ssl SMTPD_USE_SSL False Whether the fixture should use fixed TLS/SSL for transactions. If using smtplib requires that SMTP_SSL be used instead of SMTP.
use_starttls SMTPD_USE_STARTTLS False Whether the fixture should use StartTLS to encrypt the connections. If using smtplib requires that SMTP.starttls() is called before other commands are issued. Overrides use_tls as the preferred method for securing communications with the client.
enforce_auth SMTPD_ENFORCE_AUTH False If set to true then the fixture refuses MAIL, RCPT, DATA commands until authentication is completed.
ssl_cert_files SMTPD_SSL_CERT_FILE and SMTPD_SSL_KEY_FILE ("cert.pem", None) A tuple of the path for the certificate file and key file in PEM format. See Resolving certificate and key paths for more details.

Setting a custom SSL Certificate

Assuming that the certificate and key are written in a single PEM file located at ./certificates/localhost.cert.pem the following example will use the certificate for SSL encryption:

from smtplib import STMP_SSL


def test_custom_certificate(smtpd):
    smtpd.config.ssl_cert_files = "./certificates/localhost.cert.pem"
    smtpd.config.use_ssl = True

    from_ = "from.addr@example.org"
    to_ = "to.addr@example.org"
    msg = (f"From: {from_}\r\n"
           f"To: {to_}\r\n"
           f"Subject: Foo\r\n\r\n"
           f"Foo bar")

    with SMTP_SSL(smtpd.hostname, smtpd.port) as client:
        client.sendmail(from_addr, to_addrs, msg)

    assert len(smtpd.messages) == 1

Alternatives

Many libraries for sending email have built-in methods for testing and using these methods should generally be prefered over SMTPDFix. Some known solutions:

Developing

To develop and test smtpdfix you will need to install:

To install all of these in a in a virtual environment for development:

$ python -m venv venv
$ source venv/bin/activate
$ pip install -e .[dev]

Code is tested using tox:

$ tox

Quick tests can be handled by running pytest directly:

$ pytest -p no:smtpd --cov

We include a pre-commit configuration file to automate checks and clean up imports before pushing code. In order to install pre-commit git hooks:

$ pip install pre-commit
$ pre-commit install

Known Issues

  • Firewalls may interfere with the operation of the smtp server.
  • Authenticating with LOGIN and PLAIN mechanisms fails over TLS/SSL, but works with STARTTLS. Issue #10
  • Currently no support for termination through signals. Issue #4
  • If the fixture start exceeds the ready_timeout and aborts the host and port are not consistently released and subsequent uses may result in an error. Issue #80

Written with ☕ and ❤ in Montreal, QC

Release files for smtpdfix 0.5.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for smtpdfix 0.5.3
File Size Uploaded
smtpdfix-0.5.3.tar.gz 23.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for smtpdfix 0.5.3
File Interpreter ABI Platform
smtpdfix-0.5.3-py3-none-any.whl Python 3 none any Details

Total release size: 40.1 kB

Release files / smtpdfix-0.5.3.tar.gz

Download URL smtpdfix-0.5.3.tar.gz
Size 23.1 kB
Tags Source
SHA-256 checksum
How to use checksums
2ea49b22fe084c9cb92a52e56e8351c753c985eecf713c77f08516ec5e2e7fd0
BLAKE2b-256 checksum
How to use checksums
53f34c5b3d764a5f6d993ac7cd2c3a1d3f53ef965cf29dd6cf4e81abda1da123
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.0

Release files / smtpdfix-0.5.3-py3-none-any.whl

Download URL smtpdfix-0.5.3-py3-none-any.whl
Size 17.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0a75ffa84da91eb05c82a82fc447d465237035ea37fc4aaf3f608a9edc204407
BLAKE2b-256 checksum
How to use checksums
b04b468d1bc46b46c594fbc3ab44e57250c7b09236a4fc88a3ca93af09ecb396
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.0

Release history Release notifications | RSS feed

This release

0.5.3 This release

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.9

2 release files

0.2.8

2 release files

0.2.7

2 release files

0.2.6

2 release files

0.2.5

2 release files

0.2.4

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page