• Home
  • Posts
  • About Me
  • RSS
Notify changelog to Teams using Jenkins
So we can back to focus on solving problems
Jun 14, 2025 | 3 mins read
jenkins groovy tutorial

Suppose we want to start documenting the changelog, so we can easily track what new features, improvements, bugfixes are delivered in each version. To do that, we need to list the tickets done on every release version.

For this article, we want the changelog note to contain: service name, version, and list of tickets done.

Amazing Service 1.2.0

Features

  • PROJECT-1232
  • PROJECT-1231

Techdebt

  • PROJECT-1241 - improve query performance

Bugfix

  • PROJECT-1252 - fix intermittent error
  • PROJECT-1251

Other

  • PROJECT-1301

Imagine the manual work needed to do this:

  1. list the branches/tickets being merged to current release
  2. write the changelog
  3. post to Ms Teams channel

It must be done for every release version. Even doing it for a single service is already a chore. More so if you need to do it for many services, it’ll be hell. Our time will just be spent for administrative task instead of pushing our iterations quicker.

If you’re facing the same problem and already using Jenkins and Ms Teams, then this post may be for you.

Prerequisites

  1. Using Jenkins with Office365 Connector and this pipeline library:
    https://github.com/griddynamics/mpl#use-standard-pipeline-but-with-custom-module
  2. Using Ms Teams channel to post the releases
  3. Already use git tag to annotate each release
  4. Using maven release plugin and maven deploy

How to

Suppose we have this kind of merge commits between version 1.1.0 and 1.2.0:

  • Merge feature/PROJECT-1232 to release/1.2
  • Merge techdebt/PROJECT-1241-improve-query-performance to release/1.2
  • Merge feature/PROJECT-1231 to release/1.2
  • Merge bugfix/PROJECT-1252-fix-intermittent-error to release/1.2
  • Merge bugfix/PROJECT-1251 to release/1.2
  • Merge PROJECT-1301 to release/1.2

In general, we need to:

  1. Create a project-specific configuration in Jenkinsfile
  2. Create a step file: {ProjectRepo}/.jenkins/modules/{Stage}/{Name}{Stage}.groovy (name could be empty)
  3. Fill the step with our changelog notification code: we can use CFG config object & MPL functions inside the steps definition

For this case, we will use “Deploy” step with path .jenkins/modules/Deploy/Deploy.groovy.

We can check other standard steps on Jenkins Modular Pipeline Library repo: https://github.com/griddynamics/mpl/tree/master/resources/com/griddynamics/devops/mpl/modules

1. Configure some project-specific settings

@Library('jenkins-ci-automation@master') _
YourPipeline([
.....
configs: [
serviceName: "Amazing Service",
postReleaseToTeams: true,
ticketingUrl: "https://example.com/browse/",
teamsWebhookUrl: "https://{company}.webhook.office.com/webhookb2/c0fd.../JenkinsCI/d89..."
]
])
view raw Jenkinsfile hosted with ❤ by GitHub
  • ticketingUrl is, like the name suggests, the base URL of your ticketing system or issue tracker e.g. Github Issues, Jira, etc.
  • teamsWebhookUrl is the webhook URL of your teams channel which will be notified with changelog posts.

2. Create the step file

Path: .jenkins/modules/Deploy/Deploy.groovy

We want to make sure the changelog is only posted to Teams on a successful release, and not during snapshot. That means we need to execute extra logic after the original “Deploy” step. Assuming params.RELEASE_TYPE will have value RELEASE on a release build, then:

MPLModule("Deploy")
script {
if (params.RELEASE_TYPE == 'RELEASE' && CFG.configs.postReleaseToTeams) {
....
}
}
view raw if-Deploy.groovy hosted with ❤ by GitHub

3. Generate changelog & send notification to Teams

Next, we extract branch names from merge commits between previous tag and latest commit for further processing.

....
def revisionRange = 'HEAD'
try {
def previousTag = sh(
script: 'git describe --tags --abbrev=0 `git rev-list --tags --skip=1 --max-count=1`',
returnStdout: true
).trim()
revisionRange = "$previousTag..HEAD"
} catch (Exception ignoredForSimplicity) {}
// Get the branches from the commit messages between the tags
def branches = []
try {
branches = sh(
script: "/bin/bash -c 'git log $revisionRange --oneline | grep \"Merge\" | grep -Eo \"\\S*/?[A-Z]{2,}-[0-9]+\\S*\"'",
returnStdout: true
)
.trim()
.split('\n')
} catch (Exception ignoredForSimplicity) {}
....
view raw branches-Deploy.groovy hosted with ❤ by GitHub

branches value would be something like:

branches = [
'feature/PROJECT-1232',
'techdebt/PROJECT-1241-improve-query-performance',
'feature/PROJECT-1231',
'bugfix/PROJECT-1252-fix-intermittent-error',
'bugfix/PROJECT-1251',
'PROJECT-1301'
]
view raw example-branches-Deploy.groovy hosted with ❤ by GitHub

We then group branches by their type (feature, techdebt, bugfix, etc).

.....
def groupedBranches = [:]
def otherKey = 'other'
branches.each { branch ->
def parts = branch.split('/', 2)
if (parts.size() > 1) {
def type = parts[0]
def ticket = parts[1]
if (!groupedBranches.containsKey(type)) {
groupedBranches[type] = []
}
groupedBranches[type] << ticket
} else {
if (!groupedBranches.containsKey(otherKey)) {
groupedBranches[otherKey] = []
}
groupedBranches[otherKey] << branch
}
}
....
view raw groupedBranches-Deploy.groovy hosted with ❤ by GitHub

groupedBranches value would be something like:

groupedBranches = [
feature: ['PROJECT-1232', 'PROJECT-1231'],
techdebt: ['PROJECT-1241-improve-query-performance'],
bugfix: ['PROJECT-1252-fix-intermittent-error', 'PROJECT-1251'],
etc: ['PROJECT-1301']
]
view raw example-groupedBranches-Deploy.groovy hosted with ❤ by GitHub

Next is generating the markdown for the body of the Teams post. We simply list the ticket number and the description under each type.

...
def changelogMarkdown = ''
groupedBranches.each { type, tickets ->
changelogMarkdown += "**${type.capitalize()}**\n"
tickets.eachWithIndex { value, index ->
def parts = value.split('-', 3)
def ticket = "${parts[0]}-${parts[1]}"
def description = ''
if (parts.size() == 3) {
description = parts[2].replaceAll("[-_]", " ")
}
changelogMarkdown += "${index + 1}. [$ticket](${CFG.configs.ticketingUrl}$ticket)"
if (description) {
changelogMarkdown += " - $description"
}
changelogMarkdown += "\n"
}
changelogMarkdown += '\n'
}
....
view raw markdown-Deploy.groovy hosted with ❤ by GitHub

Then the generated markdown would be something like:

**Feature**
1. [PROJECT-1232](https://example.com/browse/PROJECT-1232)
2. [PROJECT-1231](https://example.com/browse/PROJECT-1231)
**Techdebt**
1. [PROJECT-1241](https://example.com/browse/PROJECT-1241) - improve query performance
**Bugfix**
1. [PROJECT-1252](https://example.com/browse/PROJECT-1252) - fix intermittent error
2. [PROJECT-1251](https://example.com/browse/PROJECT-1251)
**Etc**
1. [PROJECT-1301](https://example.com/browse/PROJECT-1301)
view raw generated-markdown-Deploy.groovy hosted with ❤ by GitHub

Finally, post using office365 connector:

office365ConnectorSend(
message: "> # **$CFG.configs.serviceName $env.RELEASE_VERSION**\n\n$changelogMarkdown",
webhookUrl: CFG.configs.teamsWebhookUrl
)
view raw notify-Deploy.groovy hosted with ❤ by GitHub

Result

For example above, once the release build is finished and deployed, Jenkins will automatically notify the changelog to the Ms Teams channel.

Amazing Service 1.2.0

Feature

  1. PROJECT-1232
  2. PROJECT-1231

Techdebt

  1. PROJECT-1241 - improve query performance

Bugfix

  1. PROJECT-1252 - fix intermittent error
  2. PROJECT-1251

Etc

  1. PROJECT-1301

This article show and example to generate a changelog and send it to a Teams channel. Using a similar method, we can send the changelog to almost anywhere with a HTTP API easily using curl or supported plugins.

That’s all Folks!

References

  • https://github.com/griddynamics/mpl
  • https://plugins.jenkins.io/Office-365-Connector/
  • https://www.jenkins.io/doc/pipeline/steps/Office-365-Connector/
jenkins groovy tutorial
‹ Previous
Why and How You Do Query Routing With Spring R2DBC

Copyright © 2025 Ray Naldo