DEV Community

tportela1
tportela1

Posted on

Don't like JIRA's Mail Handler? Extend it with SES and Lambda.

In my current work environment, Jira administration is handled by a separate team. I had created service accounts to be utilized by the Jira mail handler but then realized the mail handler lacks a lot of functionality! What do you mean you can't set component, issueType, or assignee dynamically?

Hm. That would just not do.

I decided to leverage the JIRA API and Amazon's Simple Email Service, along with Lambda.
alt text

Let's break it down step by step. First is configuring Amazon's Simple Email Service to receive the email and then consequently write it to S3. Note: the only way (currently) to extract the body of an email utilizing SES is to either write it to Simple Notification Service (SNS) or Simple Storage Service (S3). I have another article that lines out how to create this if you need help.

In Lambda, below is my code. I utilized an amazing Atlassian Python library.

import json, requests, email, boto3, urllib, time, os, base64, re
from JiraPy import *

def handler(event, context):

    s3 = boto3.client('s3')
    s3r = boto3.resource('s3')
    outputBucket = "jira"

    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.unquote_plus(event['Records'][0]['s3']['object']['key']).decode('utf8')


    try:
        if not outputBucket:
            global outputBucket
            outputBucket = bucket

        # Use waiter to ensure the file is persisted
        waiter = s3.get_waiter('object_exists')
        waiter.wait(Bucket=bucket, Key=key)
        response = s3r.Bucket(bucket).Object(key)

        # Read the raw text file into a Email Object
        msg = email.message_from_string(response.get()["Body"].read())
        messageTo = msg['To']
        messageFrom = msg['From']
        messageSubject = msg['Subject']
        messageBody = ''
        if msg.is_multipart():
            for part in msg.walk():
                ctype = part.get_content_type()
                cdispo = str(part.get('Content-Disposition'))
                # skip any text/plain (txt) attachments
                if ctype == 'text/plain' and 'attachment' not in cdispo:
                    messageBody = part.get_payload(decode=True)  
                    break
    else:
            messageBody = msg.get_payload(decode=True)

        if len(msg.get_payload()) == 2:
            attachment = msg.get_payload()[1]

        else:
            data = messageSubject.split('|')
            projectKey = data[0]
            issueType = data[1]
            component = data[2]
            summary = data[3]
            print(projectKey, issueType, messageFrom, component, summary)
            createIssue(projectKey, issueType, messageFrom, component, summary, messageBody)

    except Exception as e:
        print(e)
        raise e

The Jira portion of the script looks like:

def createIssue(projectKey, issueType, assignee, component, summary, messageBody):
    if issueType == "Epic":
        jira.issue_create(fields={
        'project': {'key': projectKey},
        'issuetype': {
            "name": issueType},
        'customfield_10605': summary,
        'assignee': {'name': assignee},
        'components': [{'name': component}],
        'summary': summary,
        'description': messageBody,
        })
    else:
        jira.issue_create(fields={
        'project': {'key': projectKey},
        'issuetype': {
            "name": issueType},
        'assignee': {'name': assignee},
        'components': [{'name': component}],
        'summary': summary,
        'description': messageBody,
        })

def updateIssue(issueKey, messageBody):
    jira.issue_add_comment(issueKey, messageBody)

Pretty simple!

Here is a visual representation of what it looks like for the end user.

alt text "Email flow for End User"

Curious to hear your thoughts, hopefully this will be useful to someone else in the same predicament.

Top comments (0)