Nobody watches a dashboard all day, so the pipeline reports changes: unsuccessful fires when a build fails, is unstable or is aborted, and fixed when one succeeds after a failure. Both call one library step:
unsuccessful { notifyTeam() }
fixed { notifyTeam() }def call(Map args = [:]) {
String result = currentBuild.currentResult
String subject = "BookNest ${env.BRANCH_NAME} #${env.BUILD_NUMBER}: ${result}"
mail to: args.get('to', 'booknest-team@example.com'), subject: subject,
body: "${subject}\n${env.BUILD_URL}\nCommit: ${env.GIT_COMMIT}\n"
String hook = args.get('webhook', env.CHAT_WEBHOOK_URL)
if (hook) {
int code = postJson(hook, JsonOutput.toJson([text: "${subject} ${env.BUILD_URL}"]))
echo "notifyTeam: webhook answered HTTP ${code}"
}
}mail (Mailer plugin) uses the SMTP server set under Manage Jenkins 8,793 > System > E-mail Notification: here Mailpit (github.com/axllent/mailpit (https://github.com/axllent/mailpit 10,468 ), MIT, v1.31.2), a mail catcher in the container l2-mailpit, so no mail leaves the machine. postJson, a @NonCPS method in the same file, posts from the controller with java.net.URL, allowed because the library is trusted, to l2-hook, a ten-line Node.js 2,131 server standing in for a Slack 353 , Teams or Discord 170 incoming webhook:
docker logs l2-hook | cut -c1-952026-09-25T16:47:31.000Z POST /booknest {"text":"BookNest feature/id-range #1: FAILURE http://l
2026-09-25T16:48:42.253Z POST /booknest {"text":"BookNest feature/id-range #2: SUCCESS http://l
A real webhook URL is a secret: keep it in a Secret text credential, not in the Jenkinsfile. The Email Extension plugin (emailext) adds templates and recipients such as the culprits since the last success.