by Jason Lutz
Over the last few months, the tech team has been trying to improve our processes, the tools we use, and of course our code itself. We got serious about project management and adopted Trello. We’ve made other development changes like starting code reviews and coding guidelines, but right now our focus has been on shoring up the development process using our chosen set of tools. So the question naturally arose … where should we have automatic integration?
The first thing that seemed natural is setting up an email to easily create trello cards for tasks that are needed. IT gets a ton of requests via email and Prod Dev will probably be getting a flood of requests/bug reports once Catalist and Acoustik hit it big time. How about a simple python script to make this happen? I set up a gmail account solely for this purpose, found a library to hook up with trello, and wrote some code to connect with gmail. Thus far I’ve had some bumps along the road, but it has been doing the job well overall. Now, the part that is actually useful to others: the code.
The first thing to find, of course, is where other people have done the work for you. Two great places is around interfacing with gmail and trello. I decided I was going to write this in python, so after some experimentation I settled on using trollop for trello and pygmail for gmail (edited some to suit my purposes). Now that I’ve got my libraries, it is a matter of wiring everything up. I need to grab all my unread gmail emails, loop through them and create a card for each, and then mark all my emails as read.
The first thing you need is pygmail. I’ll post all my code here because I took the base library, added some functions, and made a few edits where desired or needed. I started with vinod’s pygmail and ran from there. Note that the important thing is that I knew I needed two things: get unread emails (get_unread_emails) and mark unread emails as read (mark_unread_emails). To walk the message and find the body of the email, I did some stack-overflowing and made some adjustments for encoding by adding some prettifying with BeautifulSoup. Yes, I just made up the words “stack-overflowing” and “prettifying”.
from BeautifulSoup import BeautifulSoup import imaplib import email from email.parser import HeaderParser class pygmail(object): def __init__(self): self.IMAP_SERVER='imap.gmail.com' self.IMAP_PORT=993 self.M = None def login(self, username, password): self.M = imaplib.IMAP4_SSL(self.IMAP_SERVER, self.IMAP_PORT) rc, response = self.M.login(username, password) return rc, response def get_unread_emails(self, folder='Inbox'): messages =  status, count = self.M.select(folder, readonly=1) typ, data = self.M.search(None, '(UNSEEN)') for num in data.split(): typ, data = self.M.fetch(num, '(RFC822)') msg = HeaderParser().parsestr(data) msg['Body'] = self.__get_message_body__(data) messages.append(msg) return messages def __get_message_body__(self, msgData): mail = email.message_from_string(msgData) for part in mail.walk(): if part.get_content_maintype() == 'multipart' or part.get_content_subtype() != 'plain': continue return BeautifulSoup(part.get_payload()).prettify() def mark_unread_emails(self, folder='Inbox'): status, count = self.M.select(folder, readonly=0) typ, data = self.M.search(None, '(UNSEEN)') for num in data.split(): self.M.store(num.replace(' ',','),'+FLAGS','\SEEN') def logout(self): self.M.logout()
For trello, it was much simpler. Trollop is an excellent library, and the only thing I desired to add was to to easily find boards and lists by name instead of id, so I made the very simple library wrapper:
from trollop import TrelloConnection class trollopwrapper(object): def __init__(self, api_key, oauth_token): self.trelloConn = TrelloConnection(api_key, oauth_token) def get_board_from_name(self, boardName): for bd in self.trelloConn.me.boards: if bd.name == boardName: return bd return None def get_list_from_name(self, board, listName): for lst in board.lists: if lst.name == listName: return lst return None
Now to put it all together, I basically need to get my unread emails, create a new card in a specific board and list, and then mark the emails as read so you don’t do it again next time. For the card title I wanted to put the subject of the email, and for the description I wanted the from email address followed by the body of the email.
from pygmail import pygmail from trollopwrapper import trollopwrapper from datetime import datetime, date, timedelta from configobj import ConfigObj config = ConfigObj('integration.ini') def create_trello_cards_from_unread_emails(): g = pygmail() g.login(config['Gmail']['Login'], config['Gmail']['Password']) trelloConn = trollopwrapper(config['Trello']['LoginApiKey'],config['Trello']['LoginToken']) for msg in g.get_unread_emails(): companyGroup = '' msgSubject = '' # If the subject includes an @ symbol, grab what is write after it as the group within the company. # In our case, we wanted prod dev and it tasks to go to separate boards and list. if '@' in msg['Subject']: beforeAndAfterAt = msg['Subject'].split('@') beforeAndAfterEnd = beforeAndAfterAt.split(' ') companyGroup = beforeAndAfterEnd msgSubject = beforeAndAfterAt + ' ' + ' '.join(beforeAndAfterEnd[1:]) else: companyGroup = 'IT' msgSubject = msg['Subject'] cfgSect = config['Organizations'][companyGroup] thisBoard = trelloConn.get_board_from_name(cfgSect['Board']) thisList = trelloConn.get_list_from_name(thisBoard, cfgSect['List']) thisList.add_card(msgSubject, 'Created by: ' + msg['From'] + "\r\n" + msg['Body']) print "Created card for email: " + msg['Subject'] g.mark_unread_emails() g.logout()
Overall the integration has worked well, but next steps could include grabbing email attachments and maybe cleaning up formatting. For now I just need the basics, and this handles the basics.