Wiki
Clone wikirunner / Custom Mail Handler
Custom Mail Handler
Create your own mail handler, and add all features that you need using your favorite scripting language: php/groovy
Why?
If we can have custom post-functions, conditions, validators, listeners, why not to outsource to GROOVY/PHP the flow of incoming mail processing also? In the JIRA default installation the Mail Plugin provides several mail handlers which allow to create issues and/or comments from email messages, but they are poorly customizable. How about choosing an issue type of the issue being created depending on contents, or setting values of custom fields? Or analyze attachments? These default handlers are quite simple. There's a commercial In-Mail Handler for JIRA plugin, but anyway anything that is not scripted will be always limited.
Using
When you configure Custom Mail Handler, you'll see a few 'standard' options and two already familiar fields: textarea with an inline script and a path to the PHP script file. NOTE: in this case the two scripts are independent, the first one is used to configure the service only, while the second one will be evaluated by the handler to process a message.
Due to the fact that these configuration parameters for a service are stored by the Mail Plugin, there are two restrictions: the values of the fields must not contain commas, and the whole size of the key1=val1,key2=val2... configuration string must be less than 255 characters. I know, ugly.
If the inline script field is empty, it will be filled with a sample configuration. These variables you'll see will be then added to the $params
array, which will be passed to the handler init
method. Every instance of mail handler will have its own object, so no problem here. Basically: the names of the configuration variables are predetermined, but you can add your own and they will be passed to the handling script. What I recommend is to put the configuration script somewhere in the runner
directory using Script Manager, and then just include it from the inline script.
$projectKey="TEST"; $issueType="Bug"; $reporterUserName="admin"; //$reporterUserKey="admin"; $fingerprint="forward"; //forward/accept/ignore $bulk="ignore"; //ignore/accept/forward/delete $ccassignee="true"; $ccwatcher="true";
#!java evaluate(new File("../runner/params.groovy"))
- PHP
#!php require()
Why some parameters are 'outside' of inline script? It is important to keep forwardEmail 'outside' of the inline script variables, as the forwarding of unprocessed emails happens outside of handler. Yes, the design of JIRA Mail Plugin leaves much to be desired.
The script must return boolean true
to signal that it has processed email and it should be deleted.
The bindings include:
-
$message
javax.mail.Message -
$context
com.atlassian.jira.service.util.handler.MessageHandlerContext -
$monitor
com.atlassian.jira.service.util.handler.MessageHandlerExecutionMonitor -
$params
java.util.Map<String, String> -
$log
org.apache.log4j.Logger
##Examples ###GROOVY * Creation Issue
#!java import com.atlassian.crowd.embedded.api.User; $log.warn("Hello from mail handler!"); deleteEmail = false; if (!$handler.canHandleMessage($message)) { $log.debug("Cannot handle message '$subject'."); return $deleteEmail; } def project = $handler.getProject(); // ComponentAccessor.getProjectManager().getProjectObjByKey(projectKey) if (!project) { return false; } $log.warn("Reporter: '$project'."); User reporter = $handler.getReporter($message); $log.warn("Reporter: '$reporter'."); if (!reporter) { return false; } if (!$handler.canCreateIssues(reporter, project)) { return false; } //issueType = $handler.getIssueTypeObjectByName($params["issueType"]); def issueType = $handler.getIssueTypeObject(); // ComponentAccessor.getConstantsManager().getIssueTypeObject(issueType) if (!issueType) { return false; } def summary = $message.getSubject(); if (!summary) { $monitor.markMessageForDeletion("No subject, ignoring"); return false; } def priority = $handler.getPriority($message, project, issueType); // also checks field visibility def description = $handler.getDescription($message, project, issueType, reporter); // also checks field visibility def assignee = null; if ($params["ccassignee"]) { assignee = $handler.getFirstValidAssignee($message.getAllRecipients(), project); } if (!assignee) { assignee = $handler.getDefaultAssignee(project); } if (assignee) { assigneeId = assignee.getName(); // assignee.name does not work for some reason } summary = $handler.truncateSummary(summary); def issueObject = $handler.getNewIssueObject(); issueObject.setProjectObject(project); issueObject.setSummary(summary); if (description) { issueObject.setDescription(description); } $log.warn("Setting issueType to ${issueType.id}/${issueType.name}"); issueObject.setIssueTypeObject(issueType); issueObject.setReporter(reporter); if (assigneeId) { issueObject.setAssigneeId(assigneeId);} if (priority) { issueObject.setPriorityId(priority); } // Ensure issue level security is correct $handler.setDefaultSecurityLevel(issueObject); // $issue.setSecurityLevelId($levelId); $handler.setCustomFieldDefaults(issueObject); def attachments = $handler.prepareAttachmentsForMessage($message, issueObject); $log.warn("I see attachment ${attachments}"); for(AttachmentWrapper att : attachments) { $log.warn("I see attachment ${att.md5} ${att.fileName}"); if (att.getMd5() == "750DBA108138D707044355813A3D2483") { att.skip("Just because it sucks"); } if (att.getContentType() == "text/html" && (att.getFileName() == null || att.getFileName() == "null")) { $log.warn("Found an text/html attachment"); att.skip(); att.setFileName("${att.md5}-orig.html"); } } def issue = $context.createIssue(reporter, issueObject); $handler.addCcWatchersAndAttachments($message, issue, reporter); return true;
###PHP
- Creation Issue
<?php $log->warn("Hello from mail handler!"); $deleteEmail = false; if (!$handler->canHandleMessage($message)) { $log->debug("Cannot handle message '$subject'."); return $deleteEmail; } $project = $handler->getProject(); // ComponentAccessor.getProjectManager().getProjectObjByKey(projectKey) if (!$project) { return false; } $reporter = $handler->getReporter($message); if (!$reporter) { return false; } if (!$handler->canCreateIssues($reporter, $project)) { return false; } //$issueType = $handler->getIssueTypeObjectByName($params["issueType"]); $issueType = $handler->getIssueTypeObject(); // ComponentAccessor.getConstantsManager().getIssueTypeObject(issueType) if (!$issueType) { return false; } $summary = $message->getSubject(); if (!$summary) { $monitor->markMessageForDeletion("No subject, ignoring"); return false; } $summary = $handler->truncateSummary($summary); $priority = $handler->getPriority($message, $project, $issueType); // also checks field visibility $description = $handler->getDescription($message, $project, $issueType, $reporter); // also checks field visibility if ($params["ccassignee"]) { $assignee = $handler->getFirstValidAssignee($message->getAllRecipients(), $project); } if (!$assignee) { $assignee = $handler->getDefaultAssignee($project); } if ($assignee) { $assigneeId = $assignee->getName(); // $assignee->name does not work for some reason } //var_dump($assignee); $issueObject = $handler->getNewIssueObject(); $issueObject->setProjectObject($project); $issueObject->setSummary($summary); if ($description) { $issueObject->setDescription($description); } $log->warn("Setting issueType to ${issueType->id}/${issueType->name}"); $issueObject->setIssueTypeObject($issueType); $issueObject->setReporter($reporter); if ($assigneeId) { $issueObject->setAssigneeId($assigneeId);} if ($priority) { $issueObject->setPriorityId($priority); } // Ensure issue level security is correct $handler->setDefaultSecurityLevel($issueObject); // $issue->setSecurityLevelId($levelId); $handler->setCustomFieldDefaults($issueObject); $handler->setCustomFieldValue($issueObject,"customfield_10000","value from handler"); $attachments = $handler->prepareAttachmentsForMessage($message, $issueObject); foreach($attachments as $att) { $log->debug("I see attachment ${att->md5} ${att->fileName}"); if ($att->md5 == "750DBA108138D707044355813A3D2483") { $att->skip("Just because it sucks"); } if ($att->contentType == "text/html" && $att->fileName == "") { $log->warn("Found an text/html attachment"); // file_get_contents($att->file->canonicalPath) $att->skip(); $att->fileName = rand().".html"; } } $issue = $context->createIssue($reporter, $issueObject); $handler->addCcWatchersAndAttachments($message, $issue, $reporter); return true; ?>
Now for creating comments:
<?php $log->warn("Hello from mail handler!"); $subject = $message->getSubject(); if (!$handler->canHandleMessage($message)) { return false; } $log->debug("Looking for Issue Key in subject '$subject'."); $issue = $handler->findIssueObjectInString($subject); if ($issue == null) { $issue = $handler->getAssociatedIssue($message); } if ($issue == null) { return false; } $body = ($params["stripquotes"] == "yes") ? $handler->stripQuotedLines($handler->getEmailBody($message)) : $handler->getEmailBody($message); $reporter = $handler->getReporter($message); if ($context->isRealRun() && ! $handler->canAddComments($reporter, $issue)) { $errtext = "User $reporter does not have permission to add comments to $issue"; $log->warn($errtext); $context->getMonitor()->markMessageForDeletion($errtext); return true; } $attachments = $handler->prepareAttachmentsForMessage($message, $issueObject); foreach($attachments as $att) { $log->debug("I see attachment ${att->md5} ${att->fileName}"); if ($att->md5 == "750DBA108138D707044355813A3D2483") { $att->skip("Just because it sucks"); } } if ($context->isRealRun()) { $comment = $context->createComment($issue, $reporter, $body, false); $attachmentsChangeItems = $handler->createPreparedAttachments($reporter, $issue); $handler->updateHistoryAndFireEvent($attachmentsChangeItems, $issue, $reporter, $comment, false); // false = do not force Issue Commented event } return true; ?>
Debugging
Before putting handler on production, first make it solid by creating a test handler bound to a filesystem directory with a test message.
Set the plugin package
to DEBUG
in the Logging and Profiling Administration page. Also you may set com.atlassian.jira.service.services.mail
to DEBUG
. The debug messages will appear in the atlassian-jira.log, NOT in the atlassian-jira-incoming-mail.log.
Updated