I was interested in writing a super simple 10 minute email server for a while, but finally found the time. I posted the sample code on github and will try to present a breakdown of the coding components in this blog post.The app will consist of two portions, the mail server to catch the incoming emails and the Flask web app to present the emails to users.
Requirements
Python contains the mail handling functions in the standard library, as a result, we just need to install the DB abstraction and ORM
Flask ## web framework
MySQL-python ## connecting and storing emails
sqlalchemy ## used for ORM -- can replace with django or peeweee
Models
I present the models in SQLalchemy formet, but those can easily be translated to django or peewee
email_account = Table('email_account', metadata,
Column('id', Integer, primary_key=True),
Column('inbox_name', String(60), nullable=False),
Column('access_key', String(60), nullable=False),
)
emails_recieved = Table('emails_recieved', metadata,
Column('id', Integer, primary_key=True),
Column('owner_id', Integer,
ForeignKey("email_account.user_id"), nullable=False),
Column('email_desc_html', Text, nullable=False),
Column('email_from',String(100),nullable=False),
Column('email_desc_plain', Text,nullable=False),
Column('email_title', String(500),nullable=False),
)
Since this is a small test mail server, we can direct the MX records to the IP of the server. a Namecheap example will look like this (change the mailserver host IP to the right IP address)

MX records take a bit of time to change. The following python snippet will bind itself to port 25 and handle any incoming mail.
from smtpd import SMTPServer
import asyncore
import email
class TempySMTPserver(SMTPServer):
## gets called on every new email
def process_message(self, peer, mailfrom, rcpttos, data):
#print "peer " + str(peer )
#print "mailfrom" + str(mailfrom )
#print "rcpttos:" + str(rcpttos )
#print "data" + str(data)
##returns a parsed email object
parsed_email = email.message_from_string(data)
## bind the server to mail port 25
smtp_obj = TempySMTPserver(('0.0.0.0', 25), None)
try: asyncore.loop()
except KeyboardInterrupt: pass
Finally, each email can contain multiple bundled types such as HTML and plain text. while processing, it is easier to keep track of each type separately.
To avoid a specific ORM configuration, I left out the database insert statements, but a working SQLA example is located on github
for email_recpt in rcpttos:
email_username = email_recpt.split("@")[0]
##find an existing email_account for this username
#existing_account = TO-DO
## this function is a bit weird, returning a string if only one payload
pay_loads = parsed_email.get_payload()
## if we are dealing with only 1 payload, convert to array to simply the loop
if type(pay_loads) != list:
pay_loads = [pay_loads]
email_subject = parsed_email["Subject"]
html_content = ""
text_content = ""
mail_from = mailfrom
for recieved_email_type in pay_loads:
if type(recieved_email_type) == str and parsed_email.get_content_type() == "text/plain":
text_content = recieved_email_type
elif type(recieved_email_type) == str and parsed_email.get_content_type() == "text/html":
html_content = recieved_email_type
elif recieved_email_type.get_content_type() == "text/plain":
text_content = recieved_email_type.get_payload()
elif recieved_email_type.get_content_type() == "text/html":
html_content = recieved_email_type.get_payload()
## TO-DO
## insert email_subject,existing_account,html_content,text_content,mail_from into DB
## TO-DO
The Flask application is quite standard and consist of only two routes.