Tutorial - Custom message (mail) handler for JIRA


This tutorial applies to JIRA 5.0 and later.

Level of experience:

This is an advanced tutorial. You should have completed at least one intermediate tutorial before working through this tutorial. See the list of developer tutorials.

Time estimate:

It should take you approximately 1 hour to complete this tutorial.

On this page:

Overview of the tutorial

Administrators can configure JIRA to receive and process mail messages sent to a particular account on an IMAP or a POP server. Depending on the message handler selected, JIRA can create an issue or add a comment to an issue based on message content.

JIRA provides several built-in mail handlers, as described in Creating Issues and Comments from Email. In addition, plugin developers can create custom mail handlers by implementing a message-handler module.

This tutorial shows you how to build a message handler plugin. To keep things simple, the plugin will turn email content into a comment for a specific issue in a project. Also, we'll use JIRA's ability to read mail files from a local directory rather than configuring a mail server. Your completed plugin will consist of the following components:

  • Java classes encapsulating the plugin logic.
  • Resources for display of the plugin user interface (UI).
  • A plugin descriptor (XML file) to enable the plugin module in the Atlassian application.

When you have finished, all these components will be packaged in a single JAR file.

About these Instructions

You can use any supported combination of OS and IDE to construct this plugin. These instructions were written using Ubuntu Linux and IntelliJ IDEA. If you are using another combination, you should use the equivalent operations for your specific environment.

Prerequisite knowledge

To complete this tutorial, you need to know the following: 

  • The basics of Java development: classes, interfaces, methods, how to use the compiler, and so on.
  • Familiarity with development tools, such as Maven and IDEs.
  • How to create an Atlassian plugin project using the Atlassian Plugin SDK.
  • Configuring JIRA system settings.

Plugin source

We encourage you to work through this tutorial. If you want to skip ahead or check your work when you have finished, you can find the plugin source code on Atlassian Bitbucket. Bitbucket serves a public Git repository containing the tutorial's code. To clone the repository, issue the following command:

git clone https://bitbucket.org/atlassian_tutorial/jira-add-email-handler.git

Alternatively, you can download the source by choosing Downloads > Branches here: https://bitbucket.org/atlassian_tutorial/jira-add-email-handler. 

Step 1. Create the plugin project

In this step, you'll use an atlas command to generate stub code for your plugin. The atlas commands are part of the Atlassian Plugin SDK, and automate much of the work of plugin development for you.

  1. If you have not already set up the Atlassian Plugin SDK, do that now: Set up the Atlassian Plugin SDK and Build a Project.
  2. Open a terminal and navigate to the directory where you want to create the project directory.
  3. Enter the following command to create a plugin skeleton:

  4. Choose 1 for JIRA 5 when asked which version of JIRA you want to create the plugin for.

  5. As prompted, enter the following information to identify your plugin:









  6. Confirm your entries when prompted.

Step 2. Modify the POM metadata and add dependencies

It is a good idea to familiarise yourself with the project configuration file, known as the POM (Project Object Model definition file). The POM declares your plugin's dependencies, build settings, and metadata (information about your plugin).

Modify the POM as follows:

  1. Change to the mail-handler-demo directory created by the SDK.
  2. Open the pom.xml file for editing.
  3. Add your company or organisation name and your website as the name and url values of the organization element:

        <name>Example Company</name>
  4. Modify the name element to something more readable:

    <name>Mail Handler Demo</name>

    This is the name of your plugin that will appear on the Manage Add-ons page in the JIRA administration console.

  5. Update the description element:

    <description>This plugin demonstrates how to add a custom message handler to Atlassian JIRA</description>
  6. Next add the dependencies your plugin will rely on. Add the following dependencies to the ones added by the SDK:


    Notice that scope value is provided for these dependencies, since the JIRA plugin framework includes those JARs. If you were to use a different scope, it would result in classloader issues related to duplicate classes available on the plugin classpath. 

    The dependency version for jira-mail-plugin, which is listed above as ${jira.version}, can be replaced with the plugin version suitable for your version of JIRA from the following table.

    JIRA version Compatible jira-mail-plugin version
    JIRA 7.0 8.0.x 
    JIRA 6.4 7.0.21
    JIRA 6.3 6.3.15
  7. Save the pom.xml file.

Step 3. Add the message handler module to the plugin descriptor

For most plugin module types, but not all, you can use the Atlassian Plugin SDK to add modules to your plugin. The module you need for this plugin, message-handler, is one of the exceptions. You'll need to add it by hand.

  1. Open the atlassian-plugin.xml file for editing. The file is located in src/main/resources/.
  2. Add the message-handler module as a child of atlassian-plugin.

        <message-handler i18n-name-key="demohandler.name" key="demoHandler"  
                add-edit-url="/secure/admin/EditHandlerDetailsUsingParams!default.jspa" weight="0"/>

    The class attribute identifies our handler implementation class, com.example.plugins.tutorial.jira.mailhandlerdemo.DemoHandler. The weight value of 0 means that the handler will be first in the handler selection list in the administration user interface. (Built-in handlers come with a weight of 1 to 5, the lower weight the earlier in the list the handler will be displayed.) Also notice the add-edit-url value. It defines the resource used to configure our handler. For now, we've set it to a resource that comes with JIRA. We'll describe that resource, and replace it with our own a little later. 

  3. Save the file.

Step 4. Add UI text to the i18n resource file

When you created the plugin, the SDK generated an i18n resources file for you. This is where our UI text comes from. Add a UI text string to it as follows:

  1. Open the mail-handler-demo.properties resource file for editing. It is located in src/main/resources.
  2. Add the following property to the resource file:

    demohandler.name=My Demo Mail Handler

Step 5. Create the MessageHandler implementation

Finally let's create the message handler, the one referenced in the plugin descriptor. We're going to make it simple to start with, and build on this class as we go.

  1. Create a new file named DemoHandler.java in the src/main/java/com/example/plugins/tutorial/jira/mailhandlerdemo directory.
  2. Add the following contents to the file:

    package com.example.plugins.tutorial.jira.mailhandlerdemo;
    import com.atlassian.jira.service.util.handler.MessageHandler;
    import com.atlassian.jira.service.util.handler.MessageHandlerContext;
    import com.atlassian.jira.service.util.handler.MessageHandlerErrorCollector;
    import java.util.Map;
    import javax.mail.Message;
    import javax.mail.MessagingException;
    public class DemoHandler implements MessageHandler {
    	private String issueKey;
    	public void init(Map<String, String> params, MessageHandlerErrorCollector monitor) {
    	public boolean handleMessage(Message message, MessageHandlerContext context) throws MessagingException {
    		return true;

    As it turns out, our initial message handler code doesn't do a lot. But it forms a good foundation for building upon, and it gives us a chance to reflect on some concepts. Notice the methods in the class:

    • The init() method is called at message handler set up time, that is, when the administrator configures the message service and the message handler is instantiated (dependency injection works here). The message service may fetch mail via POP or IMAP, using the MailFetcherService class. Or it may read messages from a file system, using the FileService class. The params argument contains the message handler configuration. But a plugin developer can choose to keep that data elsewhere, if desired, such as in PropertySet or by using ActiveObjects. The module code may use the monitor argument to report problems spotted while initializing the handler.
    • Each time a message is successfully fetched and read, JIRA calls the handleMessage() method. The message parameter contains the message itself and context is a thin abstraction that allows you to develop handlers that work in test mode (where they should not modify JIRA) and in normal production mode. More about that soon.
  3. Save the file.

Step 6. Build, install and run the plugin

Let's start JIRA and see what we've got have so far:

  1. Open a command window and go to the plugin root directory (where the pom.xml is located).
  2. Enter this SDK command:
    This command downloads and starts JIRA with your plugin installed.
  3. Once JIRA is done starting up, open the JIRA interface in a browser.
  4. Log in with the default username and password, admin/admin.
  5. Create a simple issue tracking project when prompted.
  6. Open the cog menu and choose System.
  7. Click Incoming Mail from the left menu. It's under the section of the menu labelled Mail
  8. Click Add incoming mail handler. You should see something like this:

    That's the mail handler you added!
  9. Give your handler a name (the name can be anything; we won't be saving it this time), and click Next
    Notice the configuration form for this handler. 

    How does JIRA know what to display in this step of the wizard? It gets it from the add-edit-url parameter in your message-handler declaration. If you recall, you set that attribute to  /secure/admin/EditHandlerDetailsUsingParams!default.jspa. JIRA 5.0 and later provides this built-in resource for the benefit of legacy (JIRA 4.x) mail handlers. Legacy handlers take configuration parameters in the form of a comma-delimited list of name-value pairs. If you were to enter Handler params text in the field, such as issueKey=TST-1, otherParam=abc, your message handler's init() method would get a params map constituted of:

    issueKey    TST-1
    otherParam  abc
  10. Cancel your mail handler configuration for now. 

Let's make our Java class do something more meaningful. Also, we'll update our configuration interface resource, the add-edit-url target, for the post-5.0 world.

From here on out, you can keep JIRA running while you continue development of the plugin. To reload your plugin, use FastDev. It reinstalls your plugin behind the scenes as you work. Alternatively, you can keep the application running in one command window and use the CLI (command line interface) in another window to dynamically re-install your plugin after each change.

  1. Open a new command window and go to the plugin's root folder (where the pom.xml is located).
  2. Run atlas-cli to start the CLI.
  3. Wait until you see a message, Waiting for commands.
  4. Run pi (plugin install) to compile, package and install the plugin.
  5. Go back to your browser and test your changes. (You may need to refresh the browser page first.)
  6. Continue making changes to your code.
  7. Run pi again. And so on.

The full instructions are in the SDK guide.

Step 7: Implement a real message handler back-end

Our message handler needs one configuration parameter, the issue key defining the issue it will add comments to. We would like to validate the correctness of this parameter, whether such an issue exists, whether it's editable, and whether the sender has permissions to comment on it.

As we are going to use such validation in at least two places, let's encapsulate it as a separate component.

  1. Create a new file named IssueKeyValidator.java in the src/main/java/com/example/plugins/tutorial/jira/mailhandlerdemo directory.
  2. Give it the following contents:

    package com.example.plugins.tutorial.jira.mailhandlerdemo;
    import com.atlassian.jira.issue.Issue;
    import com.atlassian.jira.issue.IssueManager;
    import com.atlassian.jira.service.util.handler.MessageHandlerErrorCollector;
    import org.apache.commons.lang.StringUtils;
    public class IssueKeyValidator
    	private final IssueManager issueManager;
    	public IssueKeyValidator(IssueManager issueManager) {
    		this.issueManager = issueManager;
    	public Issue validateIssue(String issueKey, MessageHandlerErrorCollector collector) {
    		if (StringUtils.isBlank(issueKey)) {
    			collector.error("Issue key cannot be undefined.");
    			return null;
    		final Issue issue = issueManager.getIssueObject(issueKey);
    		if (issue == null) {
    			collector.error("Cannot add a comment from mail to issue '" + issueKey + "'. The issue does not exist.");
    			return null;
    		if (!issueManager.isEditable(issue)) {
    			collector.error("Cannot add a comment from mail to issue '" + issueKey + "'. The issue is not editable.");
    			return null;
    		return issue;
  3.  Add the validator as a component in atlassian-plugin.xml:

        <component key="issue-key-validator" class="com.example.plugins.tutorial.jira.mailhandlerdemo.IssueKeyValidator"/>
  4. Open DemoHandler.java again and replace its content with the following:

    package com.example.plugins.tutorial.jira.mailhandlerdemo;
    import com.atlassian.crowd.embedded.api.User;
    import com.atlassian.jira.issue.Issue;
    import com.atlassian.jira.service.util.handler.MessageHandler;
    import com.atlassian.jira.service.util.handler.MessageHandlerContext;
    import com.atlassian.jira.service.util.handler.MessageHandlerErrorCollector;
    import com.atlassian.jira.service.util.handler.MessageUserProcessor;
    import com.atlassian.mail.MailUtils;
    import org.apache.commons.lang.StringUtils;
    import java.util.Map;
    import javax.mail.Message;
    import javax.mail.MessagingException;
    public class DemoHandler implements MessageHandler {
    	private String issueKey;
    	private final IssueKeyValidator issueKeyValidator;
    	private final MessageUserProcessor messageUserProcessor;
    	public static final String KEY_ISSUE_KEY = "issueKey";
        // we can use dependency injection here too!
    	public DemoHandler(MessageUserProcessor messageUserProcessor, IssueKeyValidator issueKeyValidator) {
    		this.messageUserProcessor = messageUserProcessor;
    		this.issueKeyValidator = issueKeyValidator;
    	public void init(Map<String, String> params, MessageHandlerErrorCollector monitor) {
    		// getting here issue key configured by the user
    		issueKey = params.get(KEY_ISSUE_KEY);
    		if (StringUtils.isBlank(issueKey)) {
                // this message will be either logged or displayed to the user (if the handler is tested from web UI)
    			monitor.error("Issue key has not been specified ('" + KEY_ISSUE_KEY + "' parameter). This handler will not work correctly.");
    		issueKeyValidator.validateIssue(issueKey, monitor);
    	public boolean handleMessage(Message message, MessageHandlerContext context) throws MessagingException {
            // let's again validate the issue key - meanwhile issue could have been deleted, closed, etc..
    		final Issue issue = issueKeyValidator.validateIssue(issueKey, context.getMonitor());
            if (issue == null) {
                return false; // returning false means that we were unable to handle this message. It may be either
                // forwarded to specified address or left in the mail queue (if forwarding not enabled)
            // this is a small util method JIRA API provides for us, let's use it.
    		final User sender = messageUserProcessor.getAuthorFromSender(message);
    		if (sender == null) {
    			context.getMonitor().error("Message sender(s) '" + StringUtils.join(MailUtils.getSenders(message), ",")
    					+ "' do not have corresponding users in JIRA. Message will be ignored");
    			return false;
    		final String body = MailUtils.getBody(message);
    		final StringBuilder commentBody = new StringBuilder(message.getSubject());
    		if (body != null) {
    			commentBody.append("\n").append(StringUtils.abbreviate(body, 100000)); // let trim too long bodies
            // thanks to using passed context we don't need to worry about normal run vs. test run - our call
            // will be dispatched accordingly
    		context.createComment(issue, sender, commentBody.toString(), false);
    		return true; // returning true means that we have handled the message successfully. It means it will be deleted next.

    Now our code is actually doing something. The init() method simply makes sure the parameter passed in the handler configuration UI is not empty. The handleMessage() method does the work of taking a mail messages from JIRA's mail service, and making an issue comment out of it. See the code comments for line-by-line details. If you are working with JIRA 7.0 and above, the line final User sender = messageUserProcessor.getAuthorFromSender(message); should be replaced with final ApplicationUser sender = messageUserProcessor.getAuthorFromSender(message);.

  5. Save the file.

Step 8. Improve the configuration UI

So we have now a fully functional mail handler but it still has a rudimentary, error-prone UI. Let's fix it and unleash the power of the custom handler configuration UI:

  1. Add a WebWork module to your plugin by adding the following element to atlassian-plugin.xml:

        <webwork1 key="actions" name="Actions" class="java.lang.Object">
                <action name="com.example.plugins.tutorial.jira.mailhandlerdemo.EditDemoHandlerDetailsWebAction"
                    <view name="input">/view/editDemoHandlerDetails.vm</view>
                    <view name="securitybreach">/secure/views/securitybreach.jsp</view>

    This module will perform the function of rendering the configuration UI for the handler.

  2. Replace the message-handler module you added to the descriptor earlier with the following:

        <message-handler i18n-name-key="demohandler.name"
                key="demoHandler" class="com.example.plugins.tutorial.jira.mailhandlerdemo.DemoHandler"

    The message handler will now use our webwork action as the resource for adding or editing our handler settings. 

  3. Create a new file named EditDemoHandlerDetailsWebAction.java in src/main/java/com/example/plugins/tutorial/jira/mailhandlerdemo directory, and give it the following contents:

    package com.example.plugins.tutorial.jira.mailhandlerdemo;
    import com.atlassian.configurable.ObjectConfigurationException;
    import com.atlassian.jira.plugins.mail.webwork.AbstractEditHandlerDetailsWebAction;
    import com.atlassian.jira.service.JiraServiceContainer;
    import com.atlassian.jira.service.services.file.AbstractMessageHandlingService;
    import com.atlassian.jira.service.util.ServiceUtils;
    import com.atlassian.jira.util.collect.MapBuilder;
    import com.atlassian.plugin.PluginAccessor;
    import java.util.Map;
    public class EditDemoHandlerDetailsWebAction extends AbstractEditHandlerDetailsWebAction {
    	private final IssueKeyValidator issueKeyValidator;
    	public EditDemoHandlerDetailsWebAction(PluginAccessor pluginAccessor, IssueKeyValidator issueKeyValidator) {
    		this.issueKeyValidator = issueKeyValidator;
    	private String issueKey;
    	public String getIssueKey() {
    		return issueKey;
        public void setIssueKey(String issueKey) {
            this.issueKey = issueKey;
        // this method is called to let us populate our variables (or action state) 
        // with current handler settings managed by associated service (file or mail).
    	protected void copyServiceSettings(JiraServiceContainer jiraServiceContainer) throws ObjectConfigurationException {
    		final String params = jiraServiceContainer.getProperty(AbstractMessageHandlingService.KEY_HANDLER_PARAMS);
    		final Map<String, String> parameterMap = ServiceUtils.getParameterMap(params);
    		issueKey = parameterMap.get(DemoHandler.KEY_ISSUE_KEY);
    	protected Map<String, String> getHandlerParams() {
    		return MapBuilder.build(DemoHandler.KEY_ISSUE_KEY, issueKey);
    	protected void doValidation() {
    		if (configuration == null) {
    			return; // short-circuit in case we lost session, goes directly to doExecute which redirects user
    		issueKeyValidator.validateIssue(issueKey, new WebWorkErrorCollector());

    The class inherits from AbstractEditHandlerDetailsWebAction which allows us to concentrate on parameter validation. It takes care of the add, edit, and cancel handler lifecycle itself.

  4. Finally, implement the mark up used by the action. Create a new Velocity template file named editDemoHandlerDetails.vm in a directory named view under src/main/resources.

    ## couple of available navigation helpers
    #set ($modifierKey = $action.browserUtils.getModifierKey())
    #set ($submitAccessKey = $i18n.getText('AUI.form.submit.button.accesskey'))
    #set ($submitTitle = $i18n.getText('AUI.form.submit.button.tooltip', [$submitAccessKey, $modifierKey]))
    #set ($cancelAccessKey = $i18n.getText('AUI.form.cancel.link.accesskey'))
    #set ($cancelTitle = $i18n.getText('AUI.form.cancel.link.tooltip', [$cancelAccessKey, $modifierKey]))
        <form class="aui" action="EditDemoHandlerDetails.jspa" method="POST" name="mailHandlerForm" id="mailHandlerForm">
            <div class="form-body">
               <span class="global-errors-location">
                    #if ($action.getHasErrorMessages())
                        #foreach ($error in $action.getFlushedErrorMessages())
                            #AUImessage("error" "" $textutils.htmlEncode(${error}) "" "" "" "false")
                <input type="hidden" name="atl_token" value="$atl_token">
                <label for="issue-key">$i18n.getText('demohandler.issue.key')</label>
                <input type="text" class="text" id="issue-key" name="issueKey" value="$!textutils.htmlEncode($issueKey)">
            <div class="buttons-container form-footer">
                <div class="buttons">
                    #if ($action.editing)
                        #set ($addButtonLabel = $i18n.getText('common.words.save'))
                        #set ($addButtonLabel = $i18n.getText('common.forms.add'))
                    <input id="addButton" type="submit" class="button" value="$!addButtonLabel" accesskey="$submitAccessKey" title="$submitTitle">
                    <a href="IncomingMailServers.jspa" class="cancel" accesskey="$cancelAccessKey" title="$cancelTitle">$i18n.getText("AUI.form.cancel.link.text")</a>
  5. You may have noticed the demohandler.issue.key i18n key the Velocity template uses. Add a definition for this property to the resources file, mail-handler-demo.properties:

    demohandler.issue.key=Issue Key

Now try it again.

Step 9. Test the finished plugin

  1. Reload your plugin in JIRA. As mentioned, you can do this using FastDev or the pi command, or simply by restarting JIRA.
  2. If you don't already have one, create a JIRA issue that your handler can add a comment to. Note its issue key.
  3. Back in the System administration page, try adding the mail handler based on your custom handler again. As before, give the handler a name. If you like, you can configure a mail server to use as the source for the message. Otherwise, keep the default Local Files and click Next.
  4. In the handler configuration screen, enter an issue key value. Notice that if you first enter the key of an issue that doesn't exist, you get an error message, thanks to our validation code.
  5. Enter the key for an existing issue and click Add.
  6. Now let's create the mail message JIRA will pick up. Copy this content to a file named testmessage.txt in the following location: target/jira/home/import/mail.

    MIME-Version: 1.0
    Received: by with HTTP; Mon, 22 Jul 2013 13:09:38 -0700 (PDT)
    Date: Mon, 22 Jul 2013 13:09:38 -0700
    Delivered-To: admin@example.com
    Message-ID: <CAKOugVWfh27gSCxgUqJE9QTgOJUJiabS27jw@mail.gmail.com>
    Subject: Test Message Subject
    From: Admin <admin@example.com>
    To: Admin <admin@example.com>
    Content-Type: multipart/alternative; boundary=485b397dd4ab88758a04e21f414f
    Content-Type: text/plain; charset=ISO-8859-1
    Test message body.
    Content-Type: text/html; charset=ISO-8859-1
    Test message body.<br>

    The name of the file is not important. Only that each message occupies its own text file in the directory.

    The BitBucket repository for this tutorial includes a text file that contains the sample message.

  7. Watching the terminal where you started JIRA, give JIRA a few minutes to find the new message and apply it. When it does, you'll see a log message such as this:

    [INFO] [talledLocalContainer] 2013-07-23 17:29:26,784 QuartzScheduler_Worker-0 INFO ServiceRunner    MyMailHandler [atlassian.mail.incoming.fileservice] MyMailHandler[/home/atlas/atlassian/final/mail-handler-demo/target/jira/home/import/mail]: Added comment 'Test Message Subj... 'by 'admin' to issue 'TST-1'
    [INFO] [talledLocalContainer] 2013-07-23 17:29:26,784 QuartzScheduler_Worker-0 INFO ServiceRunner    MyMailHandler [atlassian.mail.incoming.fileservice] Deleted file: /home/atlas/atlassian/final/mail-handler-demo/target/jira/home/import/mail/testmessage.txt

    It worked! JIRA removed the message after applying it, as logged, so you would need to recreate the text file each time you want to test.

  8. Look at your issue again. Now you should see a new comment added by your mail handler.

Congratulations, that's it

Have a chocolate!

Related pages

Changes to MessageHandler and message processing services

Message Handler Plugin Module

Was this page helpful?

Have a question about this article?

See questions about this article

Powered by Confluence and Scroll Viewport