Send a Custom Email

This allows you to send email(s) after a transition, to support your business processes. The alternative to this is to define a new event type, new velocity templates, and map the event to the templates as documented  here.

Using this post-function is comparatively simpler and more flexible.

When setting this up in the workflow you should preview it first, which will let you test the appearance of the email, and the recipient list etc.

Taking an example. Say you wanted to send a mail to the reporter and the CCB board when an issue with Critical or above priority is created. (A more useful example might be an email requesting approval for a sub-task for instance, or letting the QA team know an issue is ready for test).

Edit your workflow, and add a Script Post-function on the transition you want the email to be sent on, then choose  Send Custom Email.

For Critical and Blocker priorities the condition might be:

groovy
issue.priority.name in ['Critical', 'Blocker']

An email template example:

Dear ${issue.assignee?.displayName},

The ${issue.issueType.name} ${issue.key} with priority ${issue.priority?.name} has been assigned to you.

Description: $issue.description

<% if (mostRecentComment)
    out << "Last comment: " << mostRecentComment
%>

Custom field value: <% out << issue.getCustomFieldValue("TextFieldB") %>

Regards,
${issue.reporter?.displayName}
Copy

Now fill in the email subject template. Clicking on the link below will pre-fill an example.

Set the email format. If you want to use an HTML template choose HTML.

Now you need to define the recipients

  • The field marked "To addresses" allows you to enter email addresses, for instance  ccb_board@acme.com  in this example.

  • "To issue fields" lets you specify fields on the issue. Both user and string fields are acceptable, for example assignee, reporter, watchers, user or user group custom fields, or custom fields holding valid email address

Recipients need to be active users in order to receive emails. You can get around this check by  adding their email to the mail object’s recipient list.

In the "Preview Issue Key" enter the key of an existing issue.

Press preview.

If there were no errors you should see something like the following:

Note the highlighted text which tells you that the condition evaluated to true, so an email would be fired in this case. You should now test with an issue that doesn’t have one of those priorities and make sure the condition evaluates to false, and for extra points, one where no priority is set at all.

Add the Send Custom Email post-function to the ToCCB_Board transition.

Finally, having tested that the recipients, the subject and email body look correct, you can add this as a post-function to your workflow.

Emails are added to your mail queue, which is flushed every one minute. If mail is disabled or no mail servers have been set up this won’t work.

Generally, where used as a post-function, this should be after the "Store the issue" function, so if you need access to links and attachments, you will get them.

If you are facing issues with characters appearing as question marks (???) then, in most cases, problems are due to a misconfiguration in the encoding, see  Jira Application internationalization and encoding troubleshooting.

Comments

You will commonly want to reference the last comment in your email templates. The following variables are available for use in the templates:

Variable name

Description

lastComment

The comment made in  this  transition, if there was one. If not this will be  null, hence you should wrap it an  if  block as shown in the example template.

In previous versions of the plugin this was set to the last comment on the issue, regardless if it was made in this transition or not. This was not the intended behaviour, but if you were relying on it then update your templates to use`mostRecentComment` instead.

mostRecentComment

The comment made in  this  transition, or if there wasn’t one, the most recent comment made prior to this transition.

latestPriorComment

The latest comment made prior to this transition. Using this, plus  lastComment, can display a conversation thread.

Additional configuration

You may notice the syntax for getting custom fields is a bit clunky, as the template engine does not allow you to use the  import  keyword. Rather than doing this, you can pass in a  config  map to the bindings for both the subject and body templates.

This is done in the  Condition and Configuration  section. Note that as this is used for the condition you must continue to return a boolean value to tell the system whether to send the email or not.

You can also pass in  closures  (functions) that you can call from the body template. A simple example of a  Condition and Configuration  section that defines config variables:

import com.atlassian.gzipfilter.org.apache.commons.lang.StringUtils
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.CustomFieldManager

def customFieldManager = ComponentAccessor.getComponent(CustomFieldManager)
def storyPointsCf = customFieldManager.getCustomFieldObjectByName("TextFieldB")

// add value of story points to config map
config.storyPoints = issue.getCustomFieldValue(storyPointsCf)

// add a closure
config.capitalize = { String str ->
    StringUtils.capitalize(str)
}

return true
Copy

The template:

Dear User,

Story Points: ${storyPoints}

Function: ${capitalize.call('hello sailor')}
Copy

Note the syntax for calling the closure from the template.

If the condition is going to return false, the config variables are irrelevant. So you can  return false  early…​

groovy
def condition = issue.status.name == "Open" && ... if (! condition) return false def expensiveConfigVars = [foo: "bar"] config.putAll (expensiveConfigVars)

Dynamic control over recipients and from

Sometimes you want to dynamically alter the recipient or the sender, depending on issue attributes. The  Condition and Configuration  section has access to an empty  mail  object (a  com.atlassian.mail.Email .

You can set attributes on it here, for example:

if (issue.priority.name == "Highest") {
    mail.setTo("head-honcho@acme.com")
} else {
    mail.setTo("chair-moistener@acme.com")
}
Copy

If you do it like this the  To  value of the configuration field becomes redundant, and you can leave it empty.

Another possible use of that field is to set email addresses stored in a custom field (that may not appear in the 'To issue fields' dropdown), for example:

import com.atlassian.jira.component.ComponentAccessor

def multiSelectAddressesCF = ComponentAccessor.getCustomFieldManager().getCustomFieldObjectByName("Multi Select Email Adresses")
def recipients = issue.getCustomFieldValue(multiSelectAddressesCF)?.join(",")

mail.setTo(recipients)
Copy

To support backwards compatibility the validation remains in place, so you will need to check the box at the bottom of the form which disables the validation of the configuration:

If you want you can construct the mail completely dynamically, including the subject and the body, and ignore the rest of the form.

Rendering wiki markup fields

If you are sending HTML mails you will want fields containing wiki markup to be converted to HTML. You can use the following code in your templates.

${helper.render(issue.description)}

Only the standard Atlassian Wiki Renderer for Jira is supported, not custom plugins supplying their own renderers.

Attachments in emails

Unlike the standard Jira email facility, this function can optionally include issue attachments in the mail. Useful for sending to offsite collaborators, who may not have Jira accounts.

You have the choice of sending none, all, new (added this transition), or you can define a custom callback for complete control over what attachments to include. There are inline examples for this.

You may need to set specific  JavaMail properties  in JIRA’s startup settings so that different mail clients will get attachments with filenames properly encoded.

For example, if an attachment’s filename is particularly long  and  contains characters outside the ASCII range, then certain versions of Microsoft Outlook won’t render the filename correctly, and users may even have trouble downloading the attachment.

To address that,  set the following properties  in your Jira instance’s startup scripts:

mail.mime.encodefilename=true mail.mime.decodefilename=true

Fair warning: your mileage may vary. Whether this is needed for your instance will be dependent on your specific environment. Implementations of  RFC 2231  vary widely across different mail clients.

If you’re trying to use the 'new' attachments setting in the listener version of this script it’s important to be aware of what 'new' will pick up.

By default, 'new' on the Issue Commented event will get any attachments found in the comment body. On Issue Created all attachments will be added. On other events, such as Issue Updated, any attachment that was added as part of that event’s changeLog will be included.

Getting old and new custom field values

For events use:

`!`<% def change = event?.getChangeLog()?.getRelated("ChildChangeItem").find {it.field == "Name of custom field"} if (change) { out << 'Old: ' + change.oldstring + "\n" out << 'New: ' + change.newstring } %>`!`

For workflow functions use:

`!`<% def change = transientVars.changeItems.find {it.field == "Name of custom field"} if (change) { out << 'Old: ' + change.oldstring + "\n" out << 'New: ' + change.newstring } %>`!`

Control over from address

By default we use the  default from address  for the SMTP server you have configured. This is because, in corporate environments, the mail relay will normally be configured to only allow a single sender. Encouraging people to set the From address caused confusion when the mail relay blocked it. Instead, you can set the  Reply-to  address, in the form, or using  mail.setReplyTo(…​).

You can also set the way the sender appears in the mail client using  mail.setFromName("Your Boss").

If you need to set the From address you can:

mail.setFrom("your-boss@example.com")

Including query results

You can include the results of a query in your email template - this will utilise the same velocity templates that Jira uses.

In the template use: /opt/avst_prod_bm_agnt1/home/xml-data/build-dir/RELEASE-SRD-RELEASE/docs/..

${helper.issueTable("project = FOO and assignee = currentUser()", 10)}
Copy

The number  10  is an optional parameter that indicates the maximum number of issues to show. If there are more than this number, we do not currently provide links to the issue navigator to retrieve them.

There is an outstanding Jira bug  JRA-40986  which prevents images being inlined into the email, so recipients will need network connectivity to the Jira instance in order for images to show.

Adding generated attachments

Sometimes you may wish to generate your own attachment and include it in the mail. Examples include:

  • an automatically generated invoice

  • a custom PDF or Office document

  • an Excel file with the output from a query

As you have access to the mail object in the  Condition and Configuration  section, you can add the attachment here, for example:

import javax.mail.internet.MimeBodyPart
import javax.mail.internet.MimeMultipart
import javax.mail.internet.MimeUtility

import javax.activation.DataHandler
import javax.activation.FileDataSource

def mp = new MimeMultipart("mixed")

def bodyPart = new MimeBodyPart()
bodyPart.setContent("some text content", "text/plain")

//def attFds = new FileDataSource("/path/to/file.xls")
//bodyPart.setDataHandler(new DataHandler(attFds))

bodyPart.setFileName(MimeUtility.encodeText("foo.txt"))
mp.addBodyPart(bodyPart)
mail.setMultipart(mp)

true
Copy

A full explanation of everything you can do with  JavaMail  is out of scope, however above we demonstrate how to add a simple text attachment, and, commented out, how to attach a file from the file system.

Sending mail from a non-issue event

The built-in script supports only issue-related events, and also "watcher added/removed" events.

If you want to send mail from an event not related to an issue, such as when a new  Version  or user is created, you can use a Custom Listener configure to handle the appropriate event, and a script such as this:

import com.atlassian.crowd.event.user.UserCreatedEvent
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.mail.Email
import com.atlassian.mail.queue.SingleMailQueueItem

def event = event as UserCreatedEvent

def email = new Email("someuser@example.com")
email.setSubject("User created")
email.setBody("User: ${event.user.displayName} created...")
// email.setMimeType("text/html") // for messages with an html body

SingleMailQueueItem item = new SingleMailQueueItem(email)
ComponentAccessor.getMailQueue().addItem(item)
Copy

Check the   documentation about constructing your string template   for the last field of the form:

As a summary, in order to call a property, you can call it with the name

"Your issue $issue has been updated."

But you can do more complex calls to your object by using the  <% %> notation:

"Your Issue $issue was updated by <% out << event.getUser() %> on <% out << event.getTime() %>."

Exclude watchers from custom email recipients

The script below makes use of the Condition and Configuration section to set the email recipients programmatically.

import com.atlassian.jira.component.ComponentAccessor

def locale = ComponentAccessor.getLocaleManager().getLocaleFor(currentUser)
def watchers = ComponentAccessor.getWatcherManager().getWatchers(issue, locale)

def cf = ComponentAccessor.getCustomFieldManager().getCustomFieldObjectByName("Multi User Picker CF")

//get the users' email addresses, in a comma separated string, that are members of the Multi User Picker custom field and are not watchers
def recipients = issue.getCustomFieldValue(cf)?.findAll { !watchers.contains(it) }?.collect {
    it.emailAddress
}?.join(",")

mail.setTo(recipients)
Copy

On this page