Костяк темы взят из главы 4 книги Ластера, но синтаксис и инструменты освежены под 2026 год. На момент написания актуальная Jenkins LTS - 2.555.1 (требует Java 21 или 25), и все шаги ниже работают на ней. Стандарт по умолчанию - declarative pipeline, именно в его post-секции и живёт вся логика оповещений.
Где живут уведомления: post-секция как контур обратной связи
Главная идея проста: уведомление - это реакция на исход сборки, а не отдельный шаг внутри stages. Поэтому отправку выносят в блок post, который выполняется после всех стадий и умеет ветвиться по статусу. Ключевые условия, которые тебе понадобятся:
- always - выполняется всегда, что бы ни случилось. Сюда вешают cleanup и публикацию отчётов.
- success - только при зелёной сборке.
- failure - сборка упала. Главный кандидат на громкое оповещение.
- unstable - сборка прошла, но тесты или линтер пометили её нестабильной.
- changed - статус изменился относительно прошлой сборки (была красной - стала зелёной, или наоборот). Идеально, чтобы не спамить.
- fixed и regression - частные случаи changed: починили после падения / сломали после успеха.
Код: Выделить всё
pipeline {
agent any
stages {
stage('Build') {
steps {
sh './gradlew clean build'
}
}
}
post {
failure {
echo 'Сборка упала - шлём громкое уведомление'
}
fixed {
echo 'Починились после красной - сообщаем, что отпустило'
}
}
}

Email через emailext: содержательные письма, а не "FAILED"
Базовый шаг mail умеет только отправить текст. Для нормальной работы ставят плагин Email Extension - он даёт шаг emailext с шаблонами, провайдерами получателей и вложениями. Именно так делают jenkins email-оповещения, которые не стыдно показать команде.
Самое полезное в emailext - recipientProviders. Вместо хардкода адресов ты говоришь "пиши тем, кто закоммитил в этот билд" через developers(), плюс инициатору запуска через requestor(). Лог сборки можно приложить вложением через attachLog. Письмо стоит слать при падении и при unstable:
Код: Выделить всё
post {
unsuccessful {
emailext(
subject: "[Jenkins] ${currentBuild.currentResult}: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
body: """\
Статус: ${currentBuild.currentResult}
Job: ${env.JOB_NAME}
Билд: #${env.BUILD_NUMBER}
Лог: ${env.BUILD_URL}console
Последний коммит сломал сборку. Подробности во вложении.""",
attachLog: true,
compressLog: true,
recipientProviders: [developers(), requestor(), culprits()]
)
}
}
Slack и Telegram: jenkins notifications прямо в рабочий чат
Почту читают не все и не сразу. Поэтому боевой канал - командный мессенджер. Для Slack ставится официальный плагин Slack Notification, в нём настраивается workspace и токен бота, а в пайплайне появляется шаг slackSend. Связка jenkins slack - это де-факто стандарт: цветной аттач, ссылка на сборку, упоминание виновника.
Код: Выделить всё
post {
failure {
slackSend(
channel: '#ci-alerts',
color: 'danger',
message: ":fire: Упала сборка *${env.JOB_NAME}* #${env.BUILD_NUMBER}\n" +
"Коммит: ${env.GIT_COMMIT?.take(8)} | <${env.BUILD_URL}console|смотреть лог>"
)
}
fixed {
slackSend(
channel: '#ci-alerts',
color: 'good',
message: ":white_check_mark: Починили *${env.JOB_NAME}* #${env.BUILD_NUMBER}"
)
}
}
Mattermost работает почти так же: ставится плагин Mattermost Notification, шаг называется mattermostSend, параметры channel/color/message повторяют Slack один в один. Если у вас self-hosted Mattermost - мигрировать с примеров Slack тривиально.
С Telegram сложнее: стабильного популярного плагина нет, и надёжнее всего слать через Bot API курлом. Создаёшь бота у @BotFather, узнаёшь chat_id, кладёшь токен в Credentials и дёргаешь sendMessage. Так jenkins telegram заводится за пять минут и не зависит от состояния очередного заброшенного плагина:
Код: Выделить всё
post {
failure {
withCredentials([string(credentialsId: 'tg-bot-token', variable: 'TG_TOKEN')]) {
sh '''
curl -s -X POST \
"https://api.telegram.org/bot${TG_TOKEN}/sendMessage" \
-d chat_id="-1001234567890" \
-d parse_mode="HTML" \
--data-urlencode "text=<b>FAILED</b> ${JOB_NAME} #${BUILD_NUMBER}
${BUILD_URL}console"
'''
}
}
}
HTML-отчёты через publishHTML: тесты и покрытие под рукой
Уведомление говорит "что-то сломалось", а отчёт показывает "что именно". JaCoCo-покрытие, Allure, отчёт линтера или results любого тест-раннера - это HTML, который генерится в workspace и пропадает после сборки. Чтобы он остался и был доступен ссылкой из карточки билда, ставят HTML Publisher Plugin и шаг publishHTML. Его место - в post { always }, потому что отчёт нужен и при провале (особенно при провале):
Код: Выделить всё
post {
always {
publishHTML(target: [
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'build/reports/tests/test',
reportFiles: 'index.html',
reportName: 'Test Report',
reportTitles: 'Результаты тестов'
])
}
}
Типичные грабли
- Спам зелёными сборками. Если слать success на каждый билд, через неделю канал замьютят, и красные сборки утонут. Шли по failure и fixed (или changed) - команде важен момент поломки и починки, а не рутинный успех.
- Бессодержательные сообщения. "Build #482 failed" бесполезно. В тексте должно быть: что за job, кто закоммитил, прямая ссылка на лог/отчёт. Иначе человек всё равно лезет в Jenkins руками.
- Падение пайплайна из-за самого уведомления. Если Slack недоступен, slackSend кинет исключение и сборка станет красной уже из-за нотификации. Оборачивай отправку в try/catch (в scripted) или принимай, что post-условие может частично упасть.
- Секреты в открытом виде. Токен Telegram или Slack webhook в Jenkinsfile - это утечка в Git. Всегда Credentials + withCredentials.
- CSP режет HTML-отчёт. Из-за Content Security Policy сложные отчёты с inline-стилями и JS могут отображаться кривrecently. Современные отчёты (Allure) с этим дружат, но если видишь голый HTML без стилей - копай в сторону CSP-заголовков Jenkins.
- Возьми любой declarative-пайплайн и добавь блок post с условиями failure, fixed и always.
- В failure повесь emailext с recipientProviders: [developers(), requestor()] и attachLog: true. Намеренно сломай сборку (sh 'exit 1') и проверь письмо.
- Заведи Telegram-бота через @BotFather, положи токен в Credentials, добавь отправку через curl в post { failure }. Убедись, что в сообщении есть JOB_NAME, BUILD_NUMBER и ссылка.
- Сгенерь любой HTML-отчёт (хоть echo в файл index.html), опубликуй через publishHTML в post { always } и открой его из карточки сборки.
- Сравни, как то же самое делается в GitLab CI (rules + integrations) и GitHub Actions (if: failure() + action для Slack) - заметишь, что идея post-условий универсальна.
- Почему отправку уведомлений размещают в post-секции, а не внутри stages? Чем отличаются условия failure, changed и fixed?
- Что делают recipientProviders developers() и culprits() в emailext и когда какой выбрать?
- Как безопасно передать токен Telegram-бота в шаг curl, не раскрывая его в Jenkinsfile?
- Почему publishHTML логично ставить в post { always }, а не в success, и за что отвечает флаг keepAll?
Хороший контур уведомлений - это failure и fixed в командный чат, развёрнутое письмо разработчику-виновнику и HTML-отчёт ссылкой из карточки. Slack и Mattermost через готовые плагины, Telegram через Bot API курлом, почта через emailext с провайдерами получателей. Главное правило: не спамить зелёными сборками и класть в каждое сообщение смысл - что упало, кто виноват, куда смотреть. Тогда CI/CD реально экономит время, а не добавляет ещё один источник шума.