Skip to main content
https://img.shields.io/pypi/v/snails.svg?style=flat-square

Sometimes you want to write a dumb email handler.

Good for: low volume, minimal parsing, interacting with legacy email-based systems

Bad for: high volume, production use, 100% RFC compliance

Requires Python 3.7+

pip install snails

Usage

import snails


def handle(msg: snails.Message) -> None:
    print(f"To: {msg['to']}")
    print(f"From: {msg['from']}")
    print("Subject: {msg['subject']}")
    for p in msg.get_payload():
        print(p.get_payload(decode=True))

# run and block until ctrl + c
snails.serve(handle, "127.0.0.1", 10025)

# or, call start/stop yourself
mailbox = snails.Mailbox(handle, "127.0.0.1", 10025)
mailbox.start()

Enable TLS

import ssl
import snails


def handle(msg: bytes) -> None:
    ...  # TODO


ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_context.load_cert_chain("cert.pem", "key.pem")

mailbox = snails.Mailbox(handle, "::", 25, ssl_context=ssl_context)

Message Parsing

When a new request arrives, snails will pass the envelope to a parser function. You can either provide this parser yourself, or let snails infer the parser based on your handler’s type annotations.

Snails provides parsers for the following types:

  • bytes

  • aiosmtpd.smtp.Envelope (aliased to snails.Envelope)

  • email.message.Message (aliased to snails.Message)

Most of the time it’s enough to use an annotation:

def handle(x: bytes):
    with open("out.log", "wb") as f:
        f.write(x)

def handle(x: snails.Envelope):
    with open("out.log", "wb") as f:
        f.write(x.content)

def handle(x: snails.Message):
    with open("out.log", "wb") as f:
        f.write(x.as_bytes())

You can also define your own parser:

def parse(e: snails.Envelope) -> dict:
    as_str = e.content.decode()
    return {}  # TODO your parsing


def handle(x: dict):
    ...  # TODO use the dict parsed above


mailbox = snails.Mailbox(handle, "::", 25, parser=parse)

Async Mailbox

Your handler and parser can both be async functions; by default snails wraps all synchronous functions.

import snails

async def parse(e: snails.Envelope) -> dict:
    as_str = e.content.decode()
    return {}  # TODO your parsing


async def handle(x: dict):
    res = await some_db_call(...)


mailbox = snails.Mailbox(handle, "::", 25, parser=parse)

Other

  • You can return a string from your handler such as "250 OK" or the built-in snails.SMTP_250.

  • Instead of snails.serve use Mailbox.start and Mailbox.stop

  • Call snails.serve with cleanup_at_exit=True to ensure Mailbox.stop is called when the interpreter is shutting down (enabled by default)

  • Call snails.serve with block=True to block execution after calling Mailbox.start (enabled by default). You can stop the server by sending SIGINT or Ctrl + C.

Metadata

Release files for snails 2.0

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

Source distribution (sdist)

Source distribution for snails 2.0
File Size Uploaded Provenance
snails-2.0.tar.gz 4.1 kB Without provenance Details

Built distribution (wheel)

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

Total release size: 9.0 kB

Release files / snails-2.0.tar.gz

Download URL snails-2.0.tar.gz
Size 4.1 kB
Tags Source
SHA-256 checksum
How to use checksums
091e126f1af9c715fa986372e9ed0ee927a69455bb3f30927b50ec83b2409f8b
BLAKE2b-256 checksum
How to use checksums
f8cffd831b93da54156c8117ed2a2611b4c730ab07a9d8a4cc4017fa12d0eebe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/1.13.0 pkginfo/1.5.0.1 requests/2.21.0 setuptools/39.0.1 requests-toolbelt/0.9.1 tqdm/4.31.1 CPython/3.7.1

Release files / snails-2.0-py3-none-any.whl

Download URL snails-2.0-py3-none-any.whl
Size 4.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0eb811d6ceec10a8820bfe7cd02b07ebf89876501e036a69e4f7325d6886d7ca
BLAKE2b-256 checksum
How to use checksums
71c2e732fb738f57b5f443320f6608198d005839eb5c2721f846e7aaf3d68e8d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/1.13.0 pkginfo/1.5.0.1 requests/2.21.0 setuptools/39.0.1 requests-toolbelt/0.9.1 tqdm/4.31.1 CPython/3.7.1

Security for snails 2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

2.0 This release

2 release files No provenance

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