minimal smtpd handler
Project description
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file snails-2.0.tar.gz
.
File metadata
- Download URL: snails-2.0.tar.gz
- Upload date:
- Size: 4.1 kB
- Tags: Source
- Uploaded using 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
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 091e126f1af9c715fa986372e9ed0ee927a69455bb3f30927b50ec83b2409f8b |
|
MD5 | ef62d1dd8ffe7fe941988f59114db4f9 |
|
BLAKE2b-256 | f8cffd831b93da54156c8117ed2a2611b4c730ab07a9d8a4cc4017fa12d0eebe |
File details
Details for the file snails-2.0-py3-none-any.whl
.
File metadata
- Download URL: snails-2.0-py3-none-any.whl
- Upload date:
- Size: 4.9 kB
- Tags: Python 3
- Uploaded using 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
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 0eb811d6ceec10a8820bfe7cd02b07ebf89876501e036a69e4f7325d6886d7ca |
|
MD5 | b1f80576f0b704dcb0931407530c846c |
|
BLAKE2b-256 | 71c2e732fb738f57b5f443320f6608198d005839eb5c2721f846e7aaf3d68e8d |