Handlers
Handlers are classes which can implement hook methods that get called at various points in the SMTP dialog.
Handlers can also be named on the command line,
but if the class’s constructor takes arguments,
you must define a @classmethod that converts the positional arguments and
returns a handler instance:
- classmethod from_cli(cls, parser, *args)
Convert the positional arguments, as strings passed in on the command line, into a handler instance.
parser is the
ArgumentParserinstance in use.If this method does not recognize the positional arguments passed in
parser, it can optionally callparser.errorwith the error message.
If from_cli() is not defined, the handler can still be used on the command
line, but its constructor cannot accept arguments.
Handler Hooks
Handlers can implement hooks that get called during the SMTP dialog, or in exceptional cases. These handler hooks are ALL called asynchronously (i.e. they are coroutines).
All handler hooks are optional and default behaviors are
carried out by the SMTP class when a hook is omitted,
so you only need to implement the ones you care about.
When a handler hook is defined, it may have additional responsibilities as described below.
Common Arguments
All handler hooks will be called with at least three arguments:
- session: Session
The session instance currently being handled, and
- envelope: Envelope
The envelope instance of the current SMTP Transaction
Some handler hooks will receive additional arguments.
Supported Hooks
The following hooks are currently supported (in alphabetical order):
- handle_AUTH(server, session, envelope, args)
Called to handle
AUTHcommand if you need custom AUTH behavior.For more information, please read the documentation for Authentication System.
- async handle_DATA(server, session, envelope) str
- Returns:
Response message to be sent to the client
Called during
DATAafter the entire message (“SMTP content” as described in RFC 5321) has been received.The content is available in
envelope.original_contentas typebytes, normalized according to the transparency rules as defined in RFC 5321, §4.5.2.In addition, the
envelope.contentattribute will also contain the contents; the type depends on whetherSMTPwas instantiated withdecode_data=Falseordecode_data=True. SeeEnvelope.contentfor more info.
- async handle_EHLO(server, session, envelope, hostname, responses) List[str]
- Parameters:
hostname (str) – The host name given by the client in the
EHLOcommand- Returns:
Response message to be sent to the client
This hook is called during
EHLO.This hook may push additional
250-<command>responses to the client by doingawait server.push(status)before returning"250 HELP"as the final response.Important
If the handler sets the
session.host_nameattribute to a false-y value (or leave it as the defaultNonevalue) it will signal later steps thatHELOfailed and need to be performed again.This also applies to the
handle_EHLO()hook below.Deprecated since version 1.3: Use the
5-argument forminstead. Support for the 4-argument form will be removed in version 2.0
- async handle_EHLO(server, session, envelope, hostname, responses) List[str]
- Parameters:
hostname (str) – The host name given by the client in the
EHLOcommandresponses (List[str]) – The ‘planned’ responses to the
EHLOcommand including the last250 HELPresponse.
- Returns:
List of response messages to be sent to the client
Called during
EHLO.The hook MUST return a list containing the desired responses. The returned list should end with
250 HELPThis hook MUST also set the :attr:
session.host_nameattribute.Important
It is strongly recommended to not change element
[0]of the list (containing the hostname of the SMTP server).
- async handle_HELO(server, session, envelope, hostname) str
- Parameters:
hostname (str) – The host name given by client during
HELO- Returns:
Response message to be sent to the client
This hook is called during
HELO.If implemented, this hook MUST also set the :attr:
session.host_nameattribute before returning'250 {}'.format(server.hostname)as the status.
- async handle_MAIL(server, session, envelope, address, mail_options) str
- Parameters:
address (str) – The parsed email address given by the client in the
MAIL FROMcommandmail_options (List[str]) – Additional ESMTP MAIL options provided by the client
- Returns:
Response message to be sent to the client
Called during
MAIL FROM.If implemented, this hook MUST also set the
envelope.mail_fromattribute and it MAY extendenvelope.mail_options(which is always a Python list).
- async handle_NOOP(server, session, envelope, arg) str
- Parameters:
arg (str) – All characters following the
NOOPcommand- Returns:
Response message to be sent to the client
Called during
NOOP.
- handle_PROXY(server, session, envelope, proxy_data)
- Parameters:
- Returns:
Truthy or Falsey, indicating if the connection may continue or not, respectively
Called during PROXY Protocol Handshake.
See PROXY Protocol Support for more information.
- async handle_QUIT(server, session, envelope) str
- Returns:
Response message to be sent to the client
Called during
QUIT.
- async handle_RCPT(server, session, envelope, address, rcpt_options) str
- Parameters:
address (str) – The parsed email address given by the client in the
RCPT TOcommandrcpt_options (List[str]) – Additional ESMTP RCPT options provided by the client
- Returns:
Response message to be sent to the client
Called during
RCPT TO.If implemented, this hook SHOULD append the address to
envelope.rcpt_tosand it MAY extendenvelope.rcpt_options(both of which are always Python lists).
- async handle_RSET(server, session, envelope) str
- Returns:
Response message to be sent to the client
Called during
RSET.
- async handle_VRFY(server, session, envelope, address) str
- Parameters:
address (str) – The parsed email address given by the client in the
VRFYcommand- Returns:
Response message to be sent to the client
Called during
VRFY.
In addition to the SMTP command hooks, the following hooks can also be implemented by handlers. These have different APIs, and are called synchronously (i.e. they are not coroutines).
- handle_STARTTLS(server, session, envelope)
If implemented, and if SSL is supported, this method gets called during the TLS handshake phase of
connection_made(). It should return True if the handshake succeeded, and False otherwise.
- handle_exception(error)
If implemented, this method is called when any error occurs during the handling of a connection (e.g. if an
smtp_<command>()method raises an exception). The exception object is passed in. This method must return a status string, such as'542 Internal server error'. If the method returnsNoneor raises an exception, an exception will be logged, and a451code will be returned to the client.Important
If client connection is lost, this handler will NOT be called.
Built-in handlers
The following built-in handlers can be imported from aiosmtpd.handlers:
- class aiosmtpd.handlers.AsyncMessage
A subclass of the
Messagehandler, it is also an abstract base class (it must be subclassed).The only difference with
Messageis thathandle_message()is called asynchronously.This class cannot be used on the command line.
- class aiosmtpd.handlers.Debugging
This class prints the contents of the received messages to a given output stream. Programmatically, you can pass the stream to print to into the constructor.
When specified on the command line, the (optional) positional argument must either be the string
stdoutorstderrindicating which stream to use. Examples:aiosmtpd -c aiosmtpd.handlers.Debugging aiosmtpd -c aiosmtpd.handlers.Debugging stderr aiosmtpd -c aiosmtpd.handlers.Debugging stdout
- class aiosmtpd.handlers.Mailbox
A subclass of the
Messagehandler which adds the messages to aMaildir. See The Mailbox Handler for details.When specified on the command line, it accepts exactly one positional argument which is the
maildir(i.e, directory where email messages will be stored.) Example:aiosmtpd -c aiosmtpd.handlers.Mailbox /home/myhome/Maildir
- class aiosmtpd.handlers.Message
This class is an abstract base class (it must be subclassed) which converts the message content into a message instance. The class used to create these instances can be passed to the constructor, and defaults to
email.message.MessageThis message instance gains a few additional headers (e.g. X-Peer, X-MailFrom, and X-RcptTo). You can override this behavior by overriding the
prepare_message()method, which takes a session and an envelope. The message instance is then passed to the handler’shandle_message()method. It is this method that must be implemented in the subclass.prepare_message()andhandle_message()`()are both called synchronously.This class cannot be used on the command line.
- class aiosmtpd.handlers.Proxy
This class is a relatively simple SMTP proxy; it forwards messages to a remote host and port. The constructor takes the host name and port as positional arguments.
This class cannot be used on the command line.
Important
Do not confuse this class with the PROXY Protocol; they are two totally different things.
- class aiosmtpd.handlers.Sink
This class just consumes and discards messages. It’s essentially the “no op” handler.
It can be used on the command line, but accepts no positional arguments. Example:
aiosmtpd -c aiosmtpd.handlers.Sink
The Mailbox Handler
A convenient handler is the Mailbox handler, which stores incoming
messages into a maildir.
To try it, let’s first prepare an ExitStack to automatically
clean up after we finish:
>>> from contextlib import ExitStack
>>> from tempfile import TemporaryDirectory
>>> # Clean up the temporary directory at the end
>>> resources = ExitStack()
>>> tempdir = resources.enter_context(TemporaryDirectory())
Then, prepare the controller:
>>> import os
>>> from aiosmtpd.controller import Controller
>>> from aiosmtpd.handlers import Mailbox
>>> #
>>> maildir_path = os.path.join(tempdir, 'maildir')
>>> controller = Controller(Mailbox(maildir_path))
>>> controller.start()
>>> # Arrange for the controller to be stopped at the end
>>> ignore = resources.callback(controller.stop)
Now we can connect to the server and send it a message…
>>> from smtplib import SMTP
>>> client = SMTP(controller.hostname, controller.port)
>>> client.sendmail('aperson@example.com', ['bperson@example.com'], """\
... From: Anne Person <anne@example.com>
... To: Bart Person <bart@example.com>
... Subject: A test
... Message-ID: <ant>
...
... Hi Bart, this is Anne.
... """)
{}
…and a second message…
>>> client.sendmail('cperson@example.com', ['dperson@example.com'], """\
... From: Cate Person <cate@example.com>
... To: Dave Person <dave@example.com>
... Subject: A test
... Message-ID: <bee>
...
... Hi Dave, this is Cate.
... """)
{}
…and a third message.
>>> client.sendmail('eperson@example.com', ['fperson@example.com'], """\
... From: Elle Person <elle@example.com>
... To: Fred Person <fred@example.com>
... Subject: A test
... Message-ID: <cat>
...
... Hi Fred, this is Elle.
... """)
{}
We open up the mailbox again, and all three messages are waiting for us.
>>> from mailbox import Maildir
>>> from operator import itemgetter
>>> mailbox = Maildir(maildir_path)
>>> messages = sorted(mailbox, key=itemgetter('message-id'))
>>> for message in messages:
... print(message['Message-ID'], message['From'], message['To'])
<ant> Anne Person <anne@example.com> Bart Person <bart@example.com>
<bee> Cate Person <cate@example.com> Dave Person <dave@example.com>
<cat> Elle Person <elle@example.com> Fred Person <fred@example.com>
Cleanup when we’re done.
>>> resources.close()