Send a Custom Email

This allows you to send email(s) when an event fires, 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 listener is comparatively simpler and more flexible.

When setting this up 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 priority is changed to Critical or above priority.

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.

Finally, having tested that the recipients, the subject and email body look correct, you can press Save.

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.

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.

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() %>."

On this page