Mail Handler for ScriptRunner allows users to run groovy scripts when a message is received, expanding on Jira’s built-in mail handling capabilities. The Mail Handler processes incoming mail, automating tasks such as creating a new issue from the mail subject, creating users from the recipient list, or triggering workflow actions.

Add Incoming Mail Handler

  1. Navigate to System→Incoming Mail→Add Incoming Mail Handler from the Administration menu.

  2. Specify a Name for the handler.

  3. In Server, select either a POP3/IMAP mail server or the Local Files option (for an external service that writes messages to the file system).

  4. Specify the Delay (in minutes); this is the frequency at which the mail handler runs.

  5. In Handler, select ScriptRunner Mail Handler.

  6. Select Next to add or write a custom groovy script.

mail handler

Configuring a Mail Handler

  1. Specify a Catch Email. Mail Handler only processes email messages whose To:, Cc:, or Bcc: lines contain the recipient specified in this field.

  2. Provide a Forward Email address. An email is sent to the given address if an error occurred during Mail Handler execution.

    This functionality is available for IMAP/POP3 configuration only.
  3. Check the Strip Quotes option to remove quoted text from the message body, and populate the context variable nonQuotedMessageBody (use this is exclude content from previous email replies).

  4. Check Create Users to create new Jira users based on the From email address, if the message comes from an unrecognised address.

  5. In Bulk, select the action that occurs for emails with the precedence Bulk, or emails with the Auto-Submitted header.

  6. To enter a script manually, type the script in the Script Console.

    Alternatively, click the File tab on the Script Console to upload a script file. Start typing the name of the file into the Script File field.

    The file must be accessible on the server.
800

The ScriptRunner Mail Handler provides the following binding variables:

  • message - The email message the handler is going to handle. message is an instance of javax.mail.Message.

  • messageHandlerContext - Jira utility class which helps to create issues, comments, and attachments etc.

  • nonQuotedMessageBody - A non-quoted message body contains lines that do not start with a '>' or '|'.

For help writing mail handler scripts, and details of utility methods, such as retrieving senders, body, and attachments from messages, see Atlassian MailUtils.

Testing a Mail Handler Script

The following should be conducted in a test environment.
  1. Configure a ScriptRunner Mail Handler for Local Files (see Add Incoming Mail Handler).

  2. Copy or write an email message in the <jira-home>/import/mail folder.

    See How to locate Jira Home Directory for details on how to locate your Jira home location. For more about file system messages, check the File System Messages section in Configuring Issues and Comments from Email.
  3. Start with a simple script such as:

    import com.atlassian.mail.MailUtils
    
    def subject = message.getSubject()
    def body = MailUtils.getBody(message)
    
    log.debug "${subject}"
    log.debug "${body}"
  4. Click the Run Now.

    A Script executed successfully notification appears if the mail handler ran as expected. If there is an error, a Cannot handle message notification appears. In the case of an unsuccessful execution, select More Details link to get more information.

    400
    400
  5. Once tested, add the configuration to your Jira instance.

Examples

Find Existing Issue

This simple example reads incoming emails and tries to find an existing issue based on the email subject, and creates a new issue if it does not exist. This example illustrates the use of MessageUserProcessor and MessageHandlerContext.

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.service.util.ServiceUtils
import com.atlassian.jira.service.util.handler.MessageUserProcessor
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.user.util.UserManager
import com.atlassian.mail.MailUtils

def userManager = ComponentAccessor.getComponent(UserManager)
def projectManager = ComponentAccessor.getProjectManager()
def issueFactory = ComponentAccessor.getIssueFactory()
def messageUserProcessor = ComponentAccessor.getComponent(MessageUserProcessor)

def subject = message.getSubject() as String
def issue = ServiceUtils.findIssueObjectInString(subject) (1)

if (issue) {
    return (2)
}

ApplicationUser user = userManager.getUserByName("admin")
ApplicationUser reporter = messageUserProcessor.getAuthorFromSender(message) ?: user (3)
def project = projectManager.getProjectObjByKey("SRTESTPRJ")

def issueObject = issueFactory.getIssue()
issueObject.setProjectObject(project)
issueObject.setSummary(subject)
issueObject.setDescription(MailUtils.getBody(message))
issueObject.setIssueTypeId(project.issueTypes.find { it.name == "Bug" }.id)
issueObject.setReporter(reporter)

messageHandlerContext.createIssue(user, issueObject) (4)
1 Uses the ServiceUtils#findIssueObjectInString function to get the issue from the subject.
2 If the issue mentioned in the subject exists, do nothing.
3 Calls MessageUserProcessor#getAuthorFromSender to get the author.
4 Uses the binding variable MessageHandlerContext to create a new issue issue.

Create Issue With Attachments

The following example is similar to the previous example but it shows how to create attachments for an issue from the available attachments in a message.

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.config.util.JiraHome
import com.atlassian.jira.service.services.file.FileService
import com.atlassian.jira.service.util.ServiceUtils
import com.atlassian.jira.service.util.handler.MessageUserProcessor
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.user.util.UserManager
import com.atlassian.mail.MailUtils
import org.apache.commons.io.FileUtils

def userManager = ComponentAccessor.getComponent(UserManager)
def projectManager = ComponentAccessor.getProjectManager()
def issueFactory = ComponentAccessor.getIssueFactory()
def messageUserProcessor = ComponentAccessor.getComponent(MessageUserProcessor)
JiraHome jiraHome = ComponentAccessor.getComponent(JiraHome)

def subject = message.getSubject() as String
def issue = ServiceUtils.findIssueObjectInString(subject)

if (issue) {
    return
}

ApplicationUser user = userManager.getUserByName("admin")
ApplicationUser reporter = messageUserProcessor.getAuthorFromSender(message) ?: user
def project = projectManager.getProjectObjByKey("SRTESTPRJ")

def issueObject = issueFactory.getIssue()
issueObject.setProjectObject(project)
issueObject.setSummary(subject)
issueObject.setDescription(MailUtils.getBody(message))
issueObject.setIssueTypeId(project.issueTypes.find { it.name == "Bug" }.id)
issueObject.setReporter(reporter)
issue = messageHandlerContext.createIssue(user, issueObject) (1)

def attachments = MailUtils.getAttachments(message) (2)

attachments.each { MailUtils.Attachment attachment ->
    def destination = new File(jiraHome.home, FileService.MAIL_DIR).getCanonicalFile()
    def file = FileUtils.getFile(destination, attachment.filename) as File (3)
    FileUtils.writeByteArrayToFile(file, attachment.contents)
    messageHandlerContext.createAttachment(file, attachment.filename, attachment.contentType, user, issue) (4)
}
1 Uses the binding variable MessageHandlerContext to create a new issue issue as the previous example.
2 Retrieve all the attachments form the message using MailUtils#getAttachments method.
3 Create a temporary file in the mail import directory to write attachment content.
4 MessageHandlerContext#createAttachment Creates and adds the attachment to the issue we have created before.

Create Users from CC and Add to Watchers

Let’s say the Jira does not have the user, and the user needs to be created. The following example reads addresses in the CC field of an email and creates users for any contacts not available:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.service.util.ServiceUtils
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.user.util.UserManager
import com.opensymphony.util.TextUtils

import javax.mail.Address
import javax.mail.Message
import javax.mail.internet.InternetAddress

def userManager = ComponentAccessor.getComponent(UserManager)
def watcherManager = ComponentAccessor.getWatcherManager()

if (userManager.hasWritableDirectory()) { (1)
    def subject = message.getSubject() as String
    def issue = ServiceUtils.findIssueObjectInString(subject)
    def addresses = message.getRecipients(Message.RecipientType.CC) as List<Address> (2)

    if (!issue || !addresses) {
        return
    }

    addresses.each {
        InternetAddress internetAddress = it as InternetAddress (3)
        def email = internetAddress.address
        if (TextUtils.verifyEmail(email)) {
            def fullName = internetAddress.getPersonal() ?: email (4)
            def user = userManager.getUserByName(fullName)

            if (!user) {
                try {
                    user = messageHandlerContext.createUser(email, null, email, fullName, null) as ApplicationUser (5)
                    log.debug "User '${user.name}' created"
                } catch (Exception ex) {
                    log.error("Cannot create user", ex)
                }
            }

            watcherManager.startWatching(user, issue) (6)
        }
    }
}
1 Checks there is at least one directory which allows creating a new user.
2 Retrieves recipients from the message where recipient type is CC.
3 Casts the address as InternetAddress which helps to get the email address, name etc.
4 In the absence of a name in InternetAddress the email address is considered to be the full name of the user.
5 Uses the messageHandlerContext binding variable to create the user.
6 Uses WatcherManager to add the user to the issue watcher list.

Change Issue Status

The following example shows how ScriptRunner Mail Handler is used to change the status of an issue. This fits a scenario where an email is generated by a service or an automated system where the message body has a structured format. In this example, the email is generated by Bitbucket server to inform the reviewers that a pull request has been merged.

The email contains the name of the branch (containing the issue key) and the pull request status.

pull req example

The mail handler uses a regular expression to read the issue key and change the issue status to Done.

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.workflow.TransitionOptions
import com.atlassian.mail.MailUtils

def issueManager = ComponentAccessor.getIssueManager()
def workflowManager = ComponentAccessor.getWorkflowManager()
def issueService = ComponentAccessor.getIssueService()

def body = MailUtils.getBody(message) (1)

def statusMatcher = /MERGED/ (2)
def statusResult = body =~ /$statusMatcher/

def issueMatcher = /SRTESTPRJ-[0-9]*/ (3)
def issueResult = body =~ /$issueMatcher/

if (statusResult.count && issueResult.count) { (4)
    def issueKey = issueResult[0] as String

    def issue = issueManager.getIssueObject(issueKey)
    if (!issue) {
        return
    }

    def action = workflowManager.getWorkflow(issue).getActionsByName("Resolve Issue").first() (5)
    assert action

    def transitionOptions = new TransitionOptions.Builder().skipConditions().skipPermissions().skipValidators().build() (6)
    def validateTransition = issueService.validateTransition(null, issue.id, action.id, issueService.newIssueInputParameters(), transitionOptions)
    def result = issueService.transition(null, validateTransition)

    if (result.errorCollection.hasAnyErrors()) {
        log.error "Error occurred while transitioning ${issue.key}"
    }
}
1 Reads the mail body from the binding variable message. In this case, the MailUtils class has been used to get the mail body.
2 Uses a regular expression to get the status of the pull request.
3 Uses a regular expression to read the issue key from the branch name.
4 Checks the result status for both of the matches above.
5 Retrieves the desired workflow action, in this case, the Done action.
6 Calls the WorkflowTransitionUtil class method to transit the issue.

Have questions? Visit the Atlassian Community to connect, share, and learn with other Atlassian users and experts, including Adaptavist staff.

Ask a question about ScriptRunner for JIRA, Bitbucket Server, or Confluence.

Want to learn more? Check out courses on Adaptavist Learn, an online platform to onboard and train new users for Atlassian solutions.