Skip to main content

An SMTP server for testing built on aiosmtpd

Project description

pytest-smtpd

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 pytest-smtpd

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",
            "pytest-smtpd",
        ],
    },
)

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

To use TLS encryption on the connections:

from smtplib import STMP_SSL  # Note the different client class.


def test_custom_certificate(smtpd):
    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

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.

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_path SMTPD_SSL_CERTS_PATH ./certs/ The path to the key and certificate in PEM format for encryption with SSL/TLS or StartTLS.
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.

Alternatives

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

Developing

To develop and test smtpdfix you will need to install pytest-asyncio to run asynchronous tests, isort to sort imports and flake8 to lint. To install in a virtual environment for development:

python -m venv venv
./venv/scripts/activate
pip install -e .[dev]

Code is tested using tox:

tox

Quick tests can be handled by running pytest directly:

pytest

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. smtpdfix Issue #10
  • Currently no support for termination through signals. smtpdfix Issue #4

Written with ☕ and ❤ in Montreal, QC

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pytest_smtpd-0.1.0.tar.gz (5.5 kB view details)

Uploaded Source

Built Distribution

pytest_smtpd-0.1.0-py3-none-any.whl (4.7 kB view details)

Uploaded Python 3

File details

Details for the file pytest_smtpd-0.1.0.tar.gz.

File metadata

  • Download URL: pytest_smtpd-0.1.0.tar.gz
  • Upload date:
  • Size: 5.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.11.0

File hashes

Hashes for pytest_smtpd-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e80cd7818f45192ebd8ec517a38d20098378e397e664ec0649da5b98a6e3ff09
MD5 09712dbcc6b16c9ba9ec7d4638f86d26
BLAKE2b-256 8d3557de31ff2bd745aafa940839125104c5150ea1d4c472946c18d99e6813fd

See more details on using hashes here.

File details

Details for the file pytest_smtpd-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pytest_smtpd-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1249e4d0ab77120eb53bce5123200b94ab6667b52a6cb5c741a3956a254c2dd8
MD5 8fbcb15d41e2d582d2c0d8f6b0a3d362
BLAKE2b-256 7bca509e8473e189e7c2702eaa8e0494f116cdb9aa5f4e4537f5dbaa93c3f096

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page