🚀 Executive Summary
TL;DR: Silent failures in GitHub Actions workflows can significantly delay development. This guide provides a robust solution by integrating GitHub Actions with Microsoft Teams to send real-time, actionable alerts for workflow failures, ensuring immediate team notification and faster resolution.
🎯 Key Takeaways
- GitHub repository secrets are essential for securely storing sensitive credentials like Microsoft Teams webhook URLs, preventing their exposure in workflow files.
- Reusable GitHub Actions workflows (triggered by
on: workflow\_call) enable modular and DRY (Don’t Repeat Yourself) notification logic, allowing multiple CI/CD pipelines to invoke a single alert mechanism. - The
if: failure()condition is crucial for triggering notification jobs exclusively when upstream jobs fail, ensuring targeted alerts for critical pipeline breakdowns and leveraging dynamic context variables likejob.statusandgithub.workflow.
Alerting on GitHub Actions Workflow Failures via Microsoft Teams
Introduction
In modern CI/CD pipelines, speed and reliability are paramount. A broken main branch or a failed deployment can block an entire team. The silent failure of a GitHub Actions workflow is a common scenario that can lead to significant delays. Developers might merge subsequent pull requests, assuming the pipeline is green, only to discover later that a critical build or test step has been failing for hours. The key to mitigating this is proactive, real-time communication.
This tutorial provides a comprehensive guide to integrating your GitHub Actions workflows with Microsoft Teams. By setting up automated alerts for failed runs, you can ensure that the right team members are notified immediately, enabling a faster response and resolution. We will create a robust and reusable notification system that sends detailed, actionable alerts directly to a designated Teams channel.
Prerequisites
Before you begin, ensure you have the following:
- Administrative access to a GitHub repository where you want to implement alerts.
- A Microsoft Teams account with permissions to add a connector to a channel.
- A basic understanding of GitHub Actions YAML syntax and repository secrets.
Step-by-Step Guide
We’ll break down the process into four clear steps: creating the Teams webhook, securing it in GitHub, building a reusable notification workflow, and finally, integrating it into your main CI/CD pipeline.
Step 1: Create an Incoming Webhook in Microsoft Teams
First, we need to generate a unique URL in Microsoft Teams that will act as the endpoint for our notifications. This is called an “Incoming Webhook.”
- Navigate to the Microsoft Teams channel where you want to receive alerts.
- Click the three dots (…) next to the channel name and select Connectors.
- In the search box, type “Incoming Webhook” and click Add, then Configure.
- Provide a name for your webhook that clearly identifies its purpose, such as “GitHub Actions Alerts”. You can also upload a custom icon to make the notifications more recognizable.
- Click Create. Teams will generate a unique URL.
- Crucially, copy this URL to your clipboard. Treat this URL as a sensitive credential, as anyone with it can post messages to your channel. Click Done.
Step 2: Store the Webhook URL as a GitHub Secret
Never hardcode sensitive information like a webhook URL directly into your workflow files. Instead, we’ll use GitHub’s encrypted secrets to store it securely.
- In your GitHub repository, go to Settings > Secrets and variables > Actions.
- Click the New repository secret button.
- For the Name, enter
MSTEAMS_WEBHOOK_URL. It’s important to use a consistent and descriptive name. - In the Secret text box, paste the webhook URL you copied from Microsoft Teams in the previous step.
- Click Add secret to save it. Now, your workflow can securely access this URL using the expression
${{ secrets.MSTEAMS_WEBHOOK_URL }}.
Step 3: Create a Reusable Notification Workflow
To avoid duplicating notification logic across multiple workflows, we will create a single, reusable workflow dedicated to sending Teams alerts. This “callable” workflow can be invoked by any other workflow in your repository.
Create a new file named notify-teams-on-failure.yml inside the .github/workflows/ directory of your repository.
# .github/workflows/notify-teams-on-failure.yml
name: Notify MS Teams on Failure
# This workflow is designed to be called by other workflows
on:
workflow_call:
inputs:
job_status:
required: true
type: string
workflow_name:
required: true
type: string
secrets:
MSTEAMS_WEBHOOK_URL:
required: true
jobs:
send-teams-notification:
runs-on: ubuntu-latest
steps:
- name: Send Teams Notification Card
env:
MSTEAMS_WEBHOOK_URL: ${{ secrets.MSTEAMS_WEBHOOK_URL }}
JOB_STATUS: ${{ inputs.job_status }}
WORKFLOW_NAME: ${{ inputs.workflow_name }}
REPO_NAME: ${{ github.repository }}
COMMIT_SHA: ${{ github.sha }}
RUN_ID: ${{ github.run_id }}
ACTOR: ${{ github.actor }}
run: |
# Bash Script
# Sanitize workflow name for JSON
WORKFLOW_NAME_SANITIZED=$(echo "$WORKFLOW_NAME" | sed 's/"/\\"/g')
# Set colors and titles based on status
if [[ "$JOB_STATUS" == "failure" ]]; then
THEME_COLOR="FF0000" # Red
STATUS_MESSAGE="Workflow Failed"
else
# Default case, can be extended for success
THEME_COLOR="00FF00" # Green
STATUS_MESSAGE="Workflow Succeeded"
fi
# Construct the Adaptive Card JSON payload
JSON_PAYLOAD=$(cat <<EOF
{
"type": "message",
"attachments": [
{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": {
"type": "AdaptiveCard",
"version": "1.2",
"msteams": { "width": "Full" },
"body": [
{
"type": "Container",
"style": "emphasis",
"items": [
{
"type": "TextBlock",
"text": "${STATUS_MESSAGE}: ${WORKFLOW_NAME_SANITIZED}",
"size": "large",
"weight": "bolder",
"wrap": true,
"color": "attention"
}
],
"bleed": true,
"backgroundColor": "${THEME_COLOR}"
},
{
"type": "FactSet",
"facts": [
{ "title": "Repository:", "value": "${REPO_NAME}" },
{ "title": "Triggered By:", "value": "${ACTOR}" },
{ "title": "Commit:", "value": "${COMMIT_SHA::7}" }
]
}
],
"actions": [
{
"type": "Action.OpenUrl",
"title": "View Workflow Run",
"url": "https://github.com/${REPO_NAME}/actions/runs/${RUN_ID}"
}
]
}
}
]
}
EOF
)
# Use curl to send the POST request
curl --request POST \
--header 'Content-Type: application/json' \
--data "${JSON_PAYLOAD}" \
"${MSTEAMS_WEBHOOK_URL}"
Code Explanation:
-
on: workflow_call: This trigger makes the workflow reusable. It can only be run when called by another workflow. -
inputsandsecrets: We define expected inputs (like the job status) and secrets (the webhook URL) that the calling workflow must provide. -
Bash Script Logic: Inside the
runstep, a bash script dynamically constructs an Adaptive Card JSON payload. This format allows for rich, well-structured notifications in Teams. -
Dynamic Values: We use GitHub Actions context variables like
${{ github.repository }}and${{ github.run_id }}to populate the card with relevant details, including a direct link to the failed workflow run. -
curlCommand: Finally,curlsends the generated JSON payload to the Teams webhook URL, creating the alert in your channel.
Step 4: Integrate the Notification into a Main CI/CD Workflow
Now, let’s modify an existing CI/CD workflow to call our new notification workflow upon failure. Here is an example of a simple build-and-test pipeline named ci.yml.
# .github/workflows/ci.yml
name: Main CI Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install Dependencies
run: npm install
- name: Run Tests
run: npm test # This step might fail
# This job runs ONLY if the 'build' job fails
notify-on-failure:
# 'needs' ensures this job runs after 'build' completes
needs: build
# 'if: failure()' is the key condition for triggering the alert
if: failure()
# This job is a "caller" that invokes our reusable workflow
uses: ./.github/workflows/notify-teams-on-failure.yml
with:
job_status: ${{ job.status }}
workflow_name: '"${{ github.workflow }}"' # Pass the workflow name
secrets:
MSTEAMS_WEBHOOK_URL: ${{ secrets.MSTEAMS_WEBHOOK_URL }}
Code Explanation:
-
needs: build: This ensures thenotify-on-failurejob waits for thebuildjob to finish before starting. -
if: failure(): This is the most critical condition. It tells GitHub Actions to run this job only if any of the jobs listed inneeds(in this case,build) has a status of “failure”. -
uses: ./.github/...: This line calls our reusable workflow from the same repository. -
withandsecrets: We pass the required inputs and secrets to the callable workflow.${{ job.status }}will dynamically resolve to “failure” when this job runs.
Common Pitfalls
-
Mismatched Secret Names: A common error is defining a secret in GitHub with one name (e.g.,
TEAMS_HOOK) but referencing another in the YAML file (e.g.,secrets.MSTEAMS_WEBHOOK_URL). The names must match exactly. If the notification fails, double-check that the secret is correctly named and passed to the callable workflow. -
Invalid JSON in the Payload: Microsoft Teams is strict about the JSON format for Adaptive Cards. A missing comma, an unescaped quote in a workflow name, or an extra bracket can cause the entire
curlrequest to be rejected, often silently. When customizing the payload, it’s wise to validate your JSON structure using an online linter before committing the changes. Our example script includes a basic sanitization step for the workflow name to help prevent this.
Conclusion
You have now successfully configured a powerful, automated alerting system for your GitHub Actions. By immediately pushing failure notifications to a centralized Microsoft Teams channel, you close the feedback loop between your CI/CD pipeline and your development team. This enhanced visibility reduces downtime, prevents broken code from being overlooked, and fosters a more responsive and resilient engineering culture. Feel free to expand on this foundation by creating different card designs for successful runs, or by routing alerts to different channels based on the repository or workflow that failed.
👉 Read the original article on TechResolve.blog
☕ Support my work
If this article helped you, you can buy me a coffee:

Top comments (0)