smtpd — Sample Mail Servers¶
The smtpd module includes classes for building simple mail transport protocol servers. It is the server-side of the protocol used by smtplib .
Mail Server Base Class¶
The base class for all of the provided example servers is SMTPServer . It handles communicating with the client, receiving incoming data, and provides a convenient hook to override to process the message once it is fully available.
The constructor arguments are the local address to listen for connections and the remote address where proxied messages should be delivered. The method process_message() is provided as a hook to be overridden by a derived class. It is called when the message is completely received, and given these arguments:
The “from” information out of the message envelope, given to the server by the client when the message is delivered. This does not necessarily match the From header in all cases.
The list of recipients from the message envelope. Again, this does not always match the To header, especially if a recipient is being blind carbon copied.
The default implementation of process_message() raises NotImplementedError . The next example defines a subclass that overrides the method to print information about the messages it receives.
import smtpd import asyncore class CustomSMTPServer(smtpd.SMTPServer): def process_message(self, peer, mailfrom, rcpttos, data): print('Receiving message from:', peer) print('Message addressed from:', mailfrom) print('Message addressed to :', rcpttos) print('Message length :', len(data)) server = CustomSMTPServer(('127.0.0.1', 1025), None) asyncore.loop()
SMTPServer uses asyncore , so to run the server call asyncore.loop() .
A client is needed to demonstrate the server. One of the examples from the section on smtplib can be adapted to create a client to send data to the test server running locally on port 1025.
import smtplib import email.utils from email.mime.text import MIMEText # Create the message msg = MIMEText('This is the body of the message.') msg['To'] = email.utils.formataddr(('Recipient', 'recipient@example.com')) msg['From'] = email.utils.formataddr(('Author', 'author@example.com')) msg['Subject'] = 'Simple test message' server = smtplib.SMTP('127.0.0.1', 1025) server.set_debuglevel(True) # show communication with the server try: server.sendmail('author@example.com', ['recipient@example.com'], msg.as_string()) finally: server.quit()
To test the programs, run smtpd_custom.py in one terminal and smtpd_senddata.py in another.
$ python3 smtpd_custom.py Receiving message from: ('127.0.0.1', 58541) Message addressed from: author@example.com Message addressed to : ['recipient@example.com'] Message length : 229
The debug output from smtpd_senddata.py shows all of the communication with the server.
$ python3 smtpd_senddata.py send: 'ehlo 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0. 0.0.0.0.0.0.ip6.arpa\r\n' reply: b'250-1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 .0.0.0.0.0.0.ip6.arpa\r\n' reply: b'250-SIZE 33554432\r\n' reply: b'250 HELP\r\n' reply: retcode (250); Msg: b'1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 .0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa\nSIZE 33554432\nHELP' send: 'mail FROM: size=236\r\n' reply: b'250 OK\r\n' reply: retcode (250); Msg: b'OK' send: 'rcpt TO:\r\n' reply: b'250 OK\r\n' reply: retcode (250); Msg: b'OK' send: 'data\r\n' reply: b'354 End data with .\r\n' reply: retcode (354); Msg: b'End data with .' data: (354, b'End data with .') send: b'Content-Type: text/plain; charset="us-ascii"\r\nMIME-Ver sion: 1.0\r\nContent-Transfer-Encoding: 7bit\r\nTo: Recipient \r\nFrom: Author \r\nSu bject: Simple test message\r\n\r\nThis is the body of the messag e.\r\n.\r\n' reply: b'250 OK\r\n' reply: retcode (250); Msg: b'OK' data: (250, b'OK') send: 'quit\r\n' reply: b'221 Bye\r\n' reply: retcode (221); Msg: b'Bye'
To stop the server, press Ctrl-C .
Debugging Server¶
The previous example shows the arguments to process_message() , but smtpd also includes a server specifically designed for more complete debugging, called DebuggingServer . It prints the entire incoming message to the console and then stops processing (it does not proxy the message to a real mail server).
import smtpd import asyncore server = smtpd.DebuggingServer(('127.0.0.1', 1025), None) asyncore.loop()
Using the smtpd_senddata.py client program from earlier, the output of the DebuggingServer is:
---------- MESSAGE FOLLOWS ---------- Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit To: Recipient From: Author Subject: Simple test message X-Peer: 127.0.0.1 This is the body of the message. ------------ END MESSAGE ------------
Proxy Server¶
The PureProxy class implements a straightforward proxy server. Incoming messages are forwarded upstream to the server given as argument to the constructor.
The standard library documentation for smtpd says, “running this has a good chance to make you into an open relay, so please be careful.”
The steps for setting up the proxy server are similar to the debug server.
import smtpd import asyncore server = smtpd.PureProxy(('127.0.0.1', 1025), ('mail', 25)) asyncore.loop()
It prints no output, though, so to verify that it is working look at the mail server logs.
Aug 20 19:16:34 homer sendmail[6785]: m9JNGXJb006785: from=author@example.com>, size=248, class=0, nrcpts=1, msgid=200810192316.m9JNGXJb006785@homer.example.com>, proto=ESMTP, daemon=MTA, relay=[192.168.1.17]
- Standard library documentation for smtpd
- smtplib – Provides a client interface.
- email – Parses email messages.
- asyncore – Base module for writing asynchronous servers.
- RFC 2822 – Internet Message Format, defines the email message format.
- RFC 5322 – Replacement for RFC 2822.
PyMOTW
If you find this information useful, consider picking up a copy of my book, The Python Standard Library By Example.
Page Contents
Navigation
This Page
Examples
The output from all the example programs from PyMOTW has been generated with Python 2.7.8, unless otherwise noted. Some of the features described here may not be available in earlier versions of Python.
If you are looking for examples that work under Python 3, please refer to the PyMOTW-3 section of the site.
Navigation
smtpd – Sample SMTP Servers¶
The smtpd module includes classes for building simple mail transport protocol servers. It is the server-side of the protocol used by smtplib .
SMTPServer¶
The base class for all of the provided example servers is SMTPServer . It handles communicating with the client, receiving the data, and provides a convenient hook to override to handle the message once it is fully available.
The constructor arguments are the local address to listen for connections and the remote address for proxying. The method process_message() is provided as a hook to be overridden by your derived class. It is called when the message is completely received, and given these arguments:
The “from” information out of the message envelope, given to the server by the client when the message is delivered. This does not necessarily match the From header in all cases.
The list of recipients from the message envelope. Again, this does not always match the To header, especially if someone is blind carbon copied.
Since the default implementation of process_message() raises NotImplementedError, to demonstrate using SMTPServer we need to create a subclass and provide a useful implementation. This first example defines a server that prints information about the messages it receives.
import smtpd import asyncore class CustomSMTPServer(smtpd.SMTPServer): def process_message(self, peer, mailfrom, rcpttos, data): print 'Receiving message from:', peer print 'Message addressed from:', mailfrom print 'Message addressed to :', rcpttos print 'Message length :', len(data) return server = CustomSMTPServer(('127.0.0.1', 1025), None) asyncore.loop()
SMTPServer uses asyncore , so to run the server we call asyncore.loop() .
Now, we need a client to send data. By adapting one of the examples from the smtplib page, we can set up a client to send data to our test server running locally on port 1025.
import smtplib import email.utils from email.mime.text import MIMEText # Create the message msg = MIMEText('This is the body of the message.') msg['To'] = email.utils.formataddr(('Recipient', 'recipient@example.com')) msg['From'] = email.utils.formataddr(('Author', 'author@example.com')) msg['Subject'] = 'Simple test message' server = smtplib.SMTP('127.0.0.1', 1025) server.set_debuglevel(True) # show communication with the server try: server.sendmail('author@example.com', ['recipient@example.com'], msg.as_string()) finally: server.quit()
Now if we run smtpd_custom.py in one terminal, and smtpd_senddata.py in another, we should see:
$ python smtpd_senddata.py send: 'ehlo farnsworth.local\r\n' reply: '502 Error: command "EHLO" not implemented\r\n' reply: retcode (502); Msg: Error: command "EHLO" not implemented send: 'helo farnsworth.local\r\n' reply: '250 farnsworth.local\r\n' reply: retcode (250); Msg: farnsworth.local send: 'mail FROM:\r\n' reply: '250 Ok\r\n' reply: retcode (250); Msg: Ok send: 'rcpt TO:\r\n' reply: '250 Ok\r\n' reply: retcode (250); Msg: Ok send: 'data\r\n' reply: '354 End data with .\r\n' reply: retcode (354); Msg: End data with . data: (354, 'End data with .') send: 'Content-Type: text/plain; charset="us-ascii"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: 7bit\r\nTo: Recipient \r\nFrom: Author \r\nSubject: Simple test message\r\n\r\nThis is the body of the message.\r\n.\r\n' reply: '250 Ok\r\n' reply: retcode (250); Msg: Ok data: (250, 'Ok') send: 'quit\r\n' reply: '221 Bye\r\n' reply: retcode (221); Msg: Bye
$ python smtpd_custom.py Receiving message from: ('127.0.0.1', 58541) Message addressed from: author@example.com Message addressed to : ['recipient@example.com'] Message length : 229
The port number for the incoming message will vary each time. Notice that the rcpttos argument is a list of values and mailfrom is a single string.
To stop the server, press Ctrl-C .
DebuggingServer¶
The example above shows the arguments to process_message() , but smtpd also includes a server specifically designed for more complete debugging, called DebuggingServer . It prints the entire incoming message to stdout and then stops processing (it does not proxy the message to a real mail server).
import smtpd import asyncore server = smtpd.DebuggingServer(('127.0.0.1', 1025), None) asyncore.loop()
Using the smtpd_senddata.py client program from above, the output of the DebuggingServer is:
$ python smtpd_debug.py ---------- MESSAGE FOLLOWS ---------- Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit To: Recipient From: Author Subject: Simple test message X-Peer: 127.0.0.1 This is the body of the message. ------------ END MESSAGE ------------
PureProxy¶
The PureProxy class implements a straightforward proxy server. Incoming messages are forwarded upstream to the server given as argument to the constructor.
The stdlib docs say, “running this has a good chance to make you into an open relay, so please be careful.”
Setting up the proxy server is just as easy as the debug server:
import smtpd import asyncore server = smtpd.PureProxy(('127.0.0.1', 1025), ('mail', 25)) asyncore.loop()
It prints no output, though, so to verify that it is working we need to look at the mail server logs.
Oct 19 19:16:34 homer sendmail[6785]: m9JNGXJb006785: from=, size=248, nrcpts=1, msgid=, proto=ESMTP, daemon=MTA, relay=[192.168.1.17]
MailmanProxy¶
smtpd also includes a special proxy that acts as a front-end for Mailman. If the local Mailman configuration recognizes the address, it is handled directly. Otherwise the message is delivered to the proxy.
smtpd Standard library documentation for this module. smtplib Provides a client interface. email Parses email messages. asyncore Base module for writing asynchronous servers. RFC 2822 Defines the email message format. GNU Mailman mailing list software An excellent example of Python software that works with email messages.
Navigation
© Copyright Doug Hellmann. | | Last updated on Jul 11, 2020. | Created using Sphinx. | Design based on «Leaves» by SmallPark |