Notifications — Slack, Email, GitHub Status Checks

7 min read

A CI pipeline that fails silently is half-built. The team needs to know that a build broke — immediately, in a channel they're already watching, with enough context to act. But the inverse problem is just as damaging: a notification system that sends alerts on every green build, every skipped step, and every scheduled run trains the team to ignore all alerts. The craft of CI notification design is sending the right information, to the right channel, at the right moment.

GitHub status checks — the primary notification

Before wiring up Slack or email, remember that GitHub already provides a notification channel built into every PR: status checks. Every workflow job that runs against a PR branch posts a result — ✅ green or ❌ red — directly on the PR conversation. Developers see it the moment they look at their PR; reviewers see it before approving; the merge button is gated on it.

For most teams, status checks are the primary notification for PR-level failures. They require zero configuration beyond having the workflow run — no Slack webhook, no email address, no token. Start here before adding anything else.

For PR failures where status checks are visible: use status checks. For nightly regression failures that aren't tied to a PR: use Slack. For formal records or escalation: use email.

Slack notifications in GitHub Actions

Slack notifications require a webhook URL. Create one in your Slack workspace (App settings → Incoming Webhooks) and store it as a repository secret named SLACK_WEBHOOK_URL.

Failure-only notification (most teams' choice):

- uses: slackapi/slack-github-action@v1
  if: failure()
  with:
    payload: |
      {
        "text": "❌ *${{ github.workflow }}* failed on `${{ github.ref_name }}`",
        "attachments": [{
          "color": "danger",
          "fields": [
            { "title": "Repository", "value": "${{ github.repository }}", "short": true },
            { "title": "Triggered by", "value": "${{ github.actor }}", "short": true },
            { "title": "Details", "value": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" }
          ]
        }]
      }
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
    SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

Status change notification (alert only when the build changes state — first failure or first recovery — to minimise noise):

- name: Notify on state change
  if: |
    (failure() && github.event.workflow_run.conclusion != 'failure') ||
    (success() && github.event.workflow_run.conclusion == 'failure')
  uses: slackapi/slack-github-action@v1
  with:
    payload: |
      { "text": "${{ job.status == 'success' && '✅ Build recovered' || '❌ Build broken' }}: ${{ github.workflow }}" }
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
    SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

State-change notifications are the highest signal-to-noise option: you're alerted when something breaks and when it's fixed, but not for every passing build.

Slack notifications in Jenkins

Jenkins Slack notifications were covered in Chapter 3, but the channel routing strategy applies everywhere:

post {
    success {
        slackSend channel: '#qa-builds', color: 'good',
                  message: "✅ *${env.JOB_NAME}* #${env.BUILD_NUMBER} passed in ${currentBuild.durationString}"
    }
    failure {
        slackSend channel: '#qa-builds', color: 'danger',
                  message: "❌ *${env.JOB_NAME}* #${env.BUILD_NUMBER} FAILED — ${env.BUILD_URL}"
    }
    unstable {
        slackSend channel: '#qa-builds', color: 'warning',
                  message: "⚠️ *${env.JOB_NAME}* #${env.BUILD_NUMBER} unstable — ${env.BUILD_URL}"
    }
}

The unstable condition matters for Jenkins QA pipelines — it fires when tests fail but the pipeline script itself succeeded. That's exactly the state where a Slack notification is most valuable: tests are failing, and without the alert, nobody would notice until they manually check the dashboard.

Email notifications

Email is slower, creates more friction, and is easier to filter into a folder nobody reads. Use it selectively:

- uses: dawidd6/action-send-mail@v3
  if: failure()
  with:
    server_address: smtp.gmail.com
    server_port: 587
    username: ${{ secrets.MAIL_USERNAME }}
    password: ${{ secrets.MAIL_PASSWORD }}
    to: qa-team@company.com
    subject: 'Build Failed: ${{ github.workflow }} on ${{ github.ref_name }}'
    body: |
      Build ${{ github.run_id }} failed.
      Commit: ${{ github.sha }}
      Author: ${{ github.actor }}
      Details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}

Appropriate email uses: weekly regression summary reports, release build notifications to stakeholders outside the Slack workspace, formal audit trails for regulated environments.

Channel routing strategy

Route notifications by severity and audience, not by tool:

EventChannelWhy
PR test failureGitHub status check (built-in)Developer sees it on their own PR immediately
Nightly regression failure#qa-builds Slack (failure only)Team-wide awareness for systemic issues
Production deployment failure#dev-alerts + #releases SlackBroader visibility, faster response
Weekly coverage reportEmail to qa-team@company.comFormal record, stakeholder visibility
Production incident@channel in #incidentsEmergency — escalate aggressively

The most common mistake is routing everything to one channel with the same message format. #general with every CI result makes #general useless. #qa-builds with every passing nightly makes #qa-builds useless. Separate channels by audience; send to each only what that audience needs to act on.

README status badges

A status badge is a small image in your repository's README that reflects the current state of a workflow:

![Tests](https://github.com/your-org/your-repo/actions/workflows/tests.yml/badge.svg)
![Nightly Regression](https://github.com/your-org/your-repo/actions/workflows/nightly.yml/badge.svg)
![Coverage](https://codecov.io/gh/your-org/your-repo/branch/main/graph/badge.svg)

Green badges signal a healthy project to developers visiting the repository, to open-source contributors evaluating whether to contribute, and to your team during onboarding. Red badges are visible immediately, prompting someone to fix the failure before it's forgotten.

Add one badge per significant workflow: PR tests, nightly regression, and coverage. More than four badges becomes visual noise.

Notification channels: right audience, right event

GitHub Status Checks

  • Audience: PR author and reviewers

    Visible on the PR — zero extra setup

  • Best for: PR-level test failures

    Immediate, in-context, actionable

  • Fires on: every workflow run on a PR

    No configuration — automatic

  • Limitation: no visibility outside GitHub

    Team members not watching the PR miss it

Slack

  • Audience: entire team or specific channel

    Best signal-to-noise with failure-only or state-change

  • Best for: nightly failures, deploy alerts

    Breaks not tied to a specific PR

  • Fires on: failure, or state change

    Configure with if: failure() or state-change logic

  • Limitation: easy to create notification fatigue

    Over-notification trains the team to ignore it

Email

  • Audience: stakeholders outside Slack

    Managers, auditors, external partners

  • Best for: formal records, weekly summaries

    Regulated environments, compliance evidence

  • Fires on: failures, release builds, reports

    Use sparingly — daily emails are ignored

  • Limitation: slow, lower urgency perceived

    Never use email for production incidents

⚠️ Common mistakes

  • Sending success notifications for every run. A Slack message for every green build adds nothing — the team already knows the build passed because it was green. Silence on success; alert on failure. The exception is nightly regression, where a "nightly passed ✅" message once a day confirms the safety net is working.
  • Using @channel in general notification jobs. @channel notifies everyone in the channel including people offline. Reserve it for production incidents and breaking main branch builds. For individual PR failures, send to #qa-builds without @channel.
  • No Slack notification for nightly regression failures. Nightly failures aren't attached to a PR, so they have no status check. Without a Slack notification, they go unnoticed until someone manually checks the pipeline — sometimes for days. Every scheduled workflow should send a notification on failure.

🎯 Practice task

Set up the notification stack for your project — 25 minutes.

  1. Add a README badge for your PR test workflow. Push. Confirm the badge updates from green to red when you push a failing test, and back to green when you fix it.
  2. Slack: create a free Slack workspace (if you don't have one), create an incoming webhook, store it as SLACK_WEBHOOK_URL. Add the slackapi/slack-github-action step with if: failure() to your nightly workflow. Trigger a nightly failure manually (workflow_dispatch) and confirm the message appears.
  3. Add a second Slack step with if: success() only to the nightly workflow. Confirm the success message appears when the nightly passes, but not on PR runs.
  4. Stretch: implement a state-change notification — only alert when the build status changes from passing to failing or vice versa. This requires comparing github.event.workflow_run.conclusion with the current job status.

You've completed Chapter 5. Chapter 6 brings all of these pieces together: a complete CI/CD pipeline for a real e-commerce application, built from scratch across four capstone lessons.

// tip to track lessons you complete and pick up where you left off across devices.