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)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| snails-2.0.tar.gz | 4.1 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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.