emailtunnel
is a small Python 3 framework that uses the smtplib
and email
standard library modules along with aiosmtpd
to implement simple mailing
list forwarding.
The user must supply a function that maps symbolic recipient addresses on their own domain to user email addresses.
A simple example, operating on the domain maillist.local
with two users
and three lists:
USERS = {
'[email protected]': ['[email protected]'],
'[email protected]': ['[email protected]'],
'[email protected]': ['[email protected]', '[email protected]'],
}
class SimpleForwarder(emailtunnel.SMTPForwarder):
def translate_recipient(self, rcptto):
try:
return USERS[rcptto]
except KeyError:
raise emailtunnel.InvalidRecipient(rcptto)
def translate_subject(self, envelope):
return '[Simple-List] %s' % envelope.message.subject
The translate_recipient
method either returns a list of external recipients
to relay the envelope to, or the empty list to silently drop the email,
or it may raise InvalidRecipient
to respond with SMTP error 550.
If another exception is raised while processing the message,
emailtunnel responds to the SMTP peer with SMTP error 451,
indicating that the error is temporary, and the peer should try again later.
In this case, the application should override handle_error
to inform the
local admin of the failure.
The framework is implemented in emailtunnel/__init__.py
,
implementing the following classes:
- InvalidRecipient (exception)
- Message (encapsulating an instance of
email.message.Message
) - Envelope (encapsulating a Message, a recipient and a sender)
- SMTPReceiver (abstract base class utilizing
aiosmtpd
) - LoggingReceiver (simple implementation of SMTPReceiver)
- RelayMixin (mixin providing envelope delivery to a relay)
- SMTPForwarder (subclass of SMTPReceiver)
The framework may be tested by running python -m emailtunnel --help
,
which runs the code in emailtunnel/__main__.py
that allows simple logging and relaying of emails.
The emailtunnel.send
module may be run from the command line to send simple
emails specified via command line parameters and standard input.
The tkmail.server
module implements TKForwarder
, an application of
emailtunnel.SMTPForwarder
.
It supports logging of exceptions and misdeliveries to a list of admins,
and it uses the tkmail.address
module to perform delicate parsing of
recipient addresses.
The TKForwarder
is started by running the tkmail
module from the command
line by calling python -m tkmail --help
.
The tkmail.monitor
module is designed to be run daily from a cron job,
and it checks the error directory and sends a report to admins.
The tkmail.test
module starts an instance of TKForwarder
,
feeds it test messages and checks the relayed messages for correctness.
When an SMTP client connects and sends an SMTP envelope,
the method SMTPReceiver.process_message
is invoked by emailtunnel
.
First, the message data is stored in an instance of Message
,
which performs a sanity roundtrip parsing check to make sure that
data == str(Message(data))
modulo trailing whitespace.
Then, the envelope is passed to handle_envelope
,
which is implemented in a subclass (such as SMTPForwarder).
If handle_envelope
returns None, emailtunnel
assumes that the envelope was
successfully delivered.
Otherwise, it must return a string, which is returned to the SMTP remote peer.
If an exception occurs in handle_envelope
, SMTP error 451 is returned to the
peer ("Requested action aborted: error in processing").
The subclass may implement handle_error
to do further logging.
The SMTPForwarder
class implements handle_envelope
by transforming the Subject via translate_subject
in the subclass
and by transforming the list of recipients via get_envelope_recipients
.
The default implementation of get_envelope_recipients
transforms each
recipient using translate_recipient
, which is the identity map by default.
The forwarded envelope has the sender provided in get_envelope_mailfrom
.
The default implementation of get_envelope_mailfrom
returns the sender
of the incoming envelope as the outgoing sender.
The envelope is passed on with only the subject changed
using RelayMixin.deliver
, which requires the attributes
relay_host
and relay_port
to be set.
If InvalidRecipient
is raised during get_envelope_recipients
, SMTP error
550 is returned to the SMTP peer (mailbox unavailable) and no email is relayed.