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),
)

Mail server

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)

Alt text

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.