Don't like JIRA's Mail Handler? Extend it with SES and Lambda.
Tags: ses, aws, lambda, automation
Published: 2019-04-10 18:36:19

---
title: Don't like JIRA's Mail Handler? Extend it with SES and Lambda.
published: true
description: Have you found Jira's mailhandler a bit lackluster? Extend functionality by utilizing Amazon's Simple Email Service, Lambda, and the Jira API.
tags: SES, AWS, Lambda, Automation
---

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](https://thepracticaldev.s3.amazonaws.com/i/nh7v0itl16elxcm60hor.png "Architecture Diagram Flow of email --> SES --> Lambda --> Atlassian API")


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] (https://atlassian-python-api.readthedocs.io/en/latest/index.html).


```python
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:

```python
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](https://thepracticaldev.s3.amazonaws.com/i/542xazfoczubp4kxh833.png) "Email flow for End User"

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