YouTrack Cloud 2024.2 Help

Send Internal Newsletters

Using a workflow to send a newsletter is an unusual use case, but it's based on our actual working process at JetBrains. One of the software developers on the YouTrack team analyzes the processes for operational teams and writes workflows that support their business logic. These teams include our accountants, legal counsels, and travel coordinators, among others. These teams use YouTrack to organize their work and communicate with other employees in our organization. Sometimes, this developer is asked to modify these workflows in a way that alters their day-to-day routine. Before she updates their projects, she sends them a bulletin that explains how the change impacts their process.

The motivation for using YouTrack to deliver this information is based on the following factors:

  • All the users who are affected by the changes are members groups that are assigned to the project team in YouTrack.

  • Every user has a registered email address stored in their user profile.

  • With workflows, YouTrack is capable of sending email messages.

This doesn't mean that you should use YouTrack to send newsletters to external users as a replacement for your email marketing platform, but it can be used as a tool for managing internal communication. YouTrack administrators and other users who apply changes to the application on a regular basis can use this technique to broadcast updates to other users whose work is impacted by these changes.

Create and Send a Newsletter

First, start with a dedicated project that contains a minimum number of fields. In this example, we use State and Recipients. Recipients is a group-type field that stores multiple values. When the State becomes Sent, an email message that contains the issue summary and description is sent to every member of these groups.

The naive approach would be to send these emails using the UserGroup.notify(subject, body) method. However, this solution is not optimal. People who belong to multiple groups in the list receive several identical messages. This example uses notifications.sendMail(message, issue) and explicitly calculates the union of the groups in a list (meaning, a list of users that belong to at least one of these groups). With this approach, each recipient receives only one message. The newsletter author is the primary recipient and all others receive a carbon copy.

The first script in this scenario collects all the users from these groups to build a single mailing list. The script is called filter:

exports.getUniqueEmails = function(groups, firstEmail) { var userEmails = [firstEmail]; groups.forEach(function(group) { group.users.forEach(function(user) { var email =; if (email && userEmails.indexOf(email) < 0) { userEmails.push(email); } }) }); return userEmails; };

A second utility script contains a function that prepares the message to be sent. Its name is composer:

exports.composeMessage = function(issue, emails) { var subject = issue.summary; var body = issue.wikify(issue.description); var link = '<a href="' + issue.url + '">' + + '</a>'; var footer = '<div style="color: #777777;">' + 'This newsletter is delivered by YouTrack. You may find the content ' + 'of this newsletter anytime at ' + link + '.' + '</div>'; return { fromName: issue.reporter.fullName, toEmails: emails, subject: subject, body: body + footer }; };

Now, armed with these two little utilities, write a rule that sends an email message when the State changes to Sent:

var entities = require('@jetbrains/youtrack-scripting-api/entities'); var notifications = require('@jetbrains/youtrack-scripting-api/notifications'); var workflow = require('@jetbrains/youtrack-scripting-api/workflow'); var composer = require('./composer'); var filter = require('./filter'); exports.rule = entities.Issue.onChange({ title: 'Send email to recipients', guard: (ctx) => { var issue = ctx.issue; return issue.isReported && issue.fields.becomes(ctx.State, ctx.State.Sent); }, action: (ctx) => { var issue = ctx.issue; workflow.check(issue.reporter.login === ctx.currentUser.login, 'Only ' + issue.reporter.fullName + ' can send this newsletter!'); var emails = filter.getUniqueEmails(issue.fields.Recipients,; var message = composer.composeMessage(issue, emails); notifications.sendEmail(message, issue); issue.fields.Recipients.forEach(function(group) { issue.permittedGroups.add(group); }); var newComment = issue.addComment('Newsletter was sent to ' + emails.length + ' recipient(s).'); newComment.permittedUsers.add(issue.reporter); }, requirements: { State: { type: entities.State.fieldType, Sent: {} }, Recipients: { type: entities.UserGroup.fieldType, multi: true } } });

Set up a Safety Net

It's best to be careful and write your workflows in a safe manner. Here are a few action rules that let you check your work before sending the newsletter. The first action rule helps you ensure that the email is sent to the right people:

var entities = require('@jetbrains/youtrack-scripting-api/entities'); var filter = require('./filter'); exports.rule = entities.Issue.action({ title: 'Dump all emails to a private comment', command: 'newsletter-dump-emails', guard: (ctx) => { return ctx.issue.isReported && ctx.issue.reporter.login === ctx.currentUser.login; }, action: (ctx) => { var issue = ctx.issue; var emails = filter.getUniqueEmails(issue.fields.Recipients,; var text = 'Newsletter will be sent to ' + emails.length + ' recipient(s):\n\n' + '```\n'; emails.forEach(function(email) { text += email + '\n'; }); text += '```\n'; var newComment = issue.addComment(text); newComment.permittedUsers.add(issue.reporter); }, requirements: { Recipients: { type: entities.UserGroup.fieldType, multi: true } } });

The second action rule sends a copy of the message to the issue reporter so you can preview the content:

var entities = require('@jetbrains/youtrack-scripting-api/entities'); var notifications = require('@jetbrains/youtrack-scripting-api/notifications'); var composer = require('./composer'); exports.rule = entities.Issue.action({ title: 'Send test email (to the reporter only)', command: 'newsletter-test-email', guard: (ctx) => { return ctx.issue.isReported && ctx.issue.reporter.login === ctx.currentUser.login; }, action: (ctx) => { var issue = ctx.issue; var emails = []; var message = composer.composeMessage(issue, emails); notifications.sendEmail(message, issue); } });

It's also possible to compose the newsletter in private and make sure other users don't see your work until it's published in the email. The following rule restricts the issue visibility to its reporter:

var entities = require('@jetbrains/youtrack-scripting-api/entities'); exports.rule = entities.Issue.onChange({ title: 'Set "Visible to" user on submit', guard: (ctx) => { return ctx.issue.becomesReported; }, action: (ctx) => { ctx.issue.permittedUsers.add(ctx.issue.reporter); } });

Here are some enhancements that you might consider:

  • Making new issues visible to a group of users who can collaborate on the newsletter.

  • Scheduling delivery based on the value that is stored in a field that stores the date and time.

  • Generating issues on a set schedule that include a template for content and a predefined send date if you deliver internal newsletters on a monthly or quarterly basis.

Last modified: 19 June 2024