Ruddra's Blog

Write RESTful API Based Email Client using Tornado

By using python's SMTP/IMAP library and an awesome framework named Tornado, we can easily make a RESTful API based email client.

At first, lets install Tornado by running pip install tornado or manually install as instructed by tornado's documentation.

We need to define imap/smtp authentication information in separate variables and we will use it in the system.

SMTP Configuration

SMTP_USERNAME = 'ME'
SMTP_PASSWORD = 'MYPASSWORD'
SMTP_HOST = 'SMTP.HOST'
SMTP_SENDER = [email protected]'

IMAP Configuration

IMAP_USERNAME = 'ME'
IMAP_PASSWORD = 'MYPASSWORD'
IMAP_SSL = 'SMTP.SSL'
IMAP_RECEIVER = [email protected]'

Then, we need to write classes inherited from tornado.web.RequestHandler and in those we shall put the SMTP/IMAP codes which will fetch/send emails. After that, we need to bind them to a url so they can be accessible from REST clients.

SMTP Request Handler

Now lets write the sending email code in a class class named SMTPRequestHandler.

import smtplib
from email.mime.text import MIMEText

class SMTPRequestHandler(tornado.web.RequestHandler):
    def post(self):
        msg = self.get_argument('body')
        subject = self.get_argument('subject')
        recipients = self.get_argument('to')

        self.send_email(msg, subject, recipients)

    def send_email(self, msg, subject, recipients):
        smtp = smtplib.SMTP()
        smtp.connect(SMTP_HOST, SMTP_PORT)
        smtp.login(SMTP_USERNAME, SMTP_PASSWORD)
        msg = MIMEText(msg)
        sender = SMTP_SENDER
        try:
            msg['Subject'] = subject
            msg['From'] = sender
            msg['To'] = recipients
            smtp.sendmail(sender, recipients, msg.as_string())
            msg = dict()
            msg['message'] = "success"
            msg['status'] = 200
            self.write(json.dumps(msg))
        except Exception as e:
            msg = dict()
            msg['message'] = "Error: {0}".format(e)
            msg['status'] = 400
            self.write(json.dumps(msg))

Code for send_email method is fairly simple, python's SMTP module smtplib has been used here. We have just connected to smtp server using the credentials we have predefined in the variables. Then we used the sendmail method to send the email to the intended addresses.

The post method in this class takes inputs subject, from, to from post from request. Then we pass those variables through send_email method. If successful, then it will return success message, else it will return Error message.

IMAP Request Handler

Now lets write code in a class class named IMAPRequestHandler which will retrieve message from inbox.

import imaplib
import email
import json

class IMAPRequestHandler(tornado.web.RequestHandler):
    def get(self):
        msg_dict = self.get_messages()
        self.write(json.dumps(msg_dict))

    def _get_text(self, email_message_instance):
        maintype = email_message_instance.get_content_maintype()
        if maintype == 'multipart':
            for part in email_message_instance.get_payload():
                if part.get_content_maintype() == 'text':
                    return part.get_payload()
        elif maintype == 'text':
            return email_message_instance.get_payload()

    def _parse_header_from_list(self, content):
        try:
            _content = email.header.decode_header(content)
            return _content[0][0]
        except IndexError:
            return 'None'

        except Exception as e:
            print(e)
            return 'None'

    def get_messages(self):
        all_unread_msg = dict()
        try:
            server = imaplib.IMAP4_SSL(IMAP_SSL, 993)
            server.login(IMAP_USERNAME, IMAP_PASSWORD)
            server.select("INBOX")
            typ, data = server.search(None, '(UNSEEN)')

            msg_dict = dict()
            for num in data[0].split():
                single_msg = dict()
                typ, data = server.fetch(num, '(RFC822)')
                msg = email.message_from_bytes(data[0][1])
                if 'subject' in msg:
                    single_msg['subject'] = self._parse_header_from_list(msg['subject'])
                if 'from' in msg:
                    single_msg['from'] = self._parse_header_from_list(msg['from'])
                if 'to' in msg:
                    single_msg['to'] = self._parse_header_from_list(msg['to'])

                single_msg['body'] = self._get_text(msg)

                msg_dict[msg['message-id']] = single_msg
            all_unread_msg['message'] = 'Successfully retrieved messages {0} messages'.format(len(msg_dict))
            all_unread_msg['emails'] = msg_dict
            all_unread_msg['status'] = 200

            server.close()
            server.logout()
        except Exception as e:
            # raise e
            all_unread_msg['message'] = "Error occurred: {0}".format(e)
            all_unread_msg['status'] = 400

        return all_unread_msg

What get_messages method does is, it logs into IMAP server, selects mailbox, mailbox's folder, then fetches the desired emails from the folder. Here we have selected the inbox and fetched the unseen emails. Then we parse subject, from, to from each message using _parse_header_from_list method, retrieve text from email's body by _get_text method. We all append all those data of each message to a dictionary using message_id as key.

When we execute the get method, it calls get_messages method. Then we get the data containing all messages and we convert in to json and return that to receiver.

Binding to Url

Final task is to bind those methods to urls like this:

application = tornado.web.Application([
    (r"/sendmail", SMTPRequestHandler),
    (r"/getmail", IMAPRequestHandler)
])

That should do the trick, you should be able to send and receive emails through RESTful API.

FYI, a working example of this blog is at here: https://github.com/ruddra/Tornado-Mail

Arnab Kumar Shil (Ruddra)
TAGGED IN Python, Tornado

comments powered by Disqus