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 in ['Critical', 'Blocker']

An email template example:

Dear ${issue.assignee?.displayName},

The ${} ${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 <<
    ) %>


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 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.


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



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.


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


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.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 ->

return true

The template:

Dear User,

Story Points: ${storyPoints}

Function: ${'hello sailor')}

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…​

def condition = == "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 ( == "Highest") {
} else {

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(",")


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.


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:


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)}

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))



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

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