Module 6: Deployment Pipelines

7. Deploy Notifications

Overview

Your pipeline deploys automatically to staging, waits for approval, and deploys to production with an automatic rollback. It works. But who finds out? If the deploy to production succeeded, does the team know? If it failed and rolled back, did anyone get an alert? If the pipeline is paused waiting for approval, does the reviewer know?

Without notifications, the deployment pipeline operates in silence. The team has to go to the Actions tab to find out what happened. That doesn't scale — nobody checks Actions proactively. Notifications turn your pipeline from a silent process into a communicative one.

In this capsule you implement notifications with the tools available in GitHub Actions: job summaries, deployment status on PRs, error/warning annotations, and echo/log patterns. Integration with Slack, email, and external services is covered in Module 7 — here you build the pipeline's communication foundation.


Notification levels

The notification spectrum

Level 1: GitHub Job Summaries
  → Visible in the workflow run's Actions tab
  → Formatted Markdown with the deploy's details
  → It requires going to Actions to see it

Level 2: Deployment Status on PRs
  → An automatic badge on PRs with the deploy's status
  → Visible without going to Actions
  → Generated by GitHub when you use environments

Level 3: Annotations (warnings/errors)
  → Inline messages in the workflow's logs
  → They appear as banners in the Actions UI
  → Useful for highlighting problems

Level 4: GitHub Notifications
  → GitHub's native notifications (email, mobile)
  → Automatic for deployment reviews
  → Configurable per user

Level 5: External Notifications (Module 7)
  → Slack, Teams, Discord, email
  → It requires a webhook or API integration
  → Covered in Module 7

This capsule covers levels 1-4. You'll implement level 5 in Module 7.


Job Summaries — The deploy report

What a Job Summary is

GitHub Actions lets every job generate a Markdown summary that appears in the workflow run's tab. It gets written to the $GITHUB_STEP_SUMMARY file.

- name: Deploy summary
  run: |
    echo "### Deployment Report" >> $GITHUB_STEP_SUMMARY
    echo "" >> $GITHUB_STEP_SUMMARY
    echo "**Status:** ✅ Success" >> $GITHUB_STEP_SUMMARY
    echo "**Environment:** production" >> $GITHUB_STEP_SUMMARY
    echo "**Image:** \`sha-abc1234\`" >> $GITHUB_STEP_SUMMARY

The summary appears at the top of the workflow run in the Actions UI. It's the first thing someone sees when they open the run.

A complete summary for a successful deploy

- name: Deploy success summary
  if: success()
  run: |
    echo "### ✅ Deployment Successful" >> $GITHUB_STEP_SUMMARY
    echo "" >> $GITHUB_STEP_SUMMARY
    echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
    echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
    echo "| **Environment** | production |" >> $GITHUB_STEP_SUMMARY
    echo "| **Image** | \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}\` |" >> $GITHUB_STEP_SUMMARY
    echo "| **Commit** | [\`$(echo ${{ github.sha }} | cut -c1-7)\`](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}) |" >> $GITHUB_STEP_SUMMARY
    echo "| **Author** | @${{ github.actor }} |" >> $GITHUB_STEP_SUMMARY
    echo "| **URL** | [your-app.com](https://your-app.com) |" >> $GITHUB_STEP_SUMMARY
    echo "" >> $GITHUB_STEP_SUMMARY
    echo "**Health check:** \`/health\` → 200 OK" >> $GITHUB_STEP_SUMMARY

A summary for a failed deploy with a rollback

- name: Deploy failure summary
  if: failure()
  run: |
    echo "### ❌ Deployment Failed — Rolled Back" >> $GITHUB_STEP_SUMMARY
    echo "" >> $GITHUB_STEP_SUMMARY
    echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
    echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
    echo "| **Environment** | production |" >> $GITHUB_STEP_SUMMARY
    echo "| **Failed image** | \`sha-${{ github.sha }}\` |" >> $GITHUB_STEP_SUMMARY
    echo "| **Rolled back to** | \`${{ steps.current.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
    echo "| **Commit** | [\`$(echo ${{ github.sha }} | cut -c1-7)\`](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}) |" >> $GITHUB_STEP_SUMMARY
    echo "| **Author** | @${{ github.actor }} |" >> $GITHUB_STEP_SUMMARY
    echo "" >> $GITHUB_STEP_SUMMARY
    echo "**Action required:** Investigate the failure and fix before next deploy." >> $GITHUB_STEP_SUMMARY
    echo "" >> $GITHUB_STEP_SUMMARY
    echo "**Logs:** Check the workflow logs for the health check failure." >> $GITHUB_STEP_SUMMARY

A summary of the complete pipeline (multi-job)

Each job can write its own summary. The result is a combined report:

jobs:
  test:
    steps:
      - name: Test summary
        if: always()
        run: |
          echo "### Tests" >> $GITHUB_STEP_SUMMARY
          echo "- Lint: ✅" >> $GITHUB_STEP_SUMMARY
          echo "- Unit tests: ✅ (42 passed)" >> $GITHUB_STEP_SUMMARY
          echo "- Prompt regression: ✅" >> $GITHUB_STEP_SUMMARY

  build:
    steps:
      - name: Build summary
        if: always()
        run: |
          echo "### Docker Build" >> $GITHUB_STEP_SUMMARY
          echo "- Image: \`${{ steps.meta.outputs.tags }}\`" >> $GITHUB_STEP_SUMMARY
          echo "- Size: $(docker images --format '{{.Size}}' | head -1)" >> $GITHUB_STEP_SUMMARY
          echo "- Cache: hit" >> $GITHUB_STEP_SUMMARY

  deploy-staging:
    steps:
      - name: Staging summary
        run: |
          echo "### Staging Deploy" >> $GITHUB_STEP_SUMMARY
          echo "- URL: [staging.your-app.com](https://staging.your-app.com)" >> $GITHUB_STEP_SUMMARY
          echo "- Health: ✅ OK" >> $GITHUB_STEP_SUMMARY

  deploy-production:
    steps:
      - name: Production summary
        if: always()
        run: |
          echo "### Production Deploy" >> $GITHUB_STEP_SUMMARY
          if [ "${{ steps.verify.outcome }}" = "success" ]; then
            echo "- Status: ✅ Deployed" >> $GITHUB_STEP_SUMMARY
          else
            echo "- Status: ❌ Failed (rolled back)" >> $GITHUB_STEP_SUMMARY
          fi

Deployment Status on PRs

An automatic status with environments

When you use environment: in a job, GitHub automatically shows the deployment status on the related PRs:

deploy-staging:
  environment:
    name: staging
    url: https://staging.your-app.com

The result on the PR:

PR #42: "Add embeddings endpoint"

  Checks:
    ✅ test — All tests passed
    ✅ build — Docker image built
    🚀 staging — Deployed (View deployment →)
    ⏸️ production — Waiting for review

The "View deployment" link leads to the URL you configured in the environment. The reviewer can click and see staging directly.

The deployment status badge

GitHub also shows a badge in the repo's Deployments tab:

GitHub → your-repo → Deployments

staging:
  ✅ Active — sha-abc1234 — 5 minutes ago
  
production:
  ⏸️ Pending — Waiting for review

This history updates automatically. You don't need to configure anything — GitHub does it when the job with environment: runs.


Annotations — Warnings and Errors

What annotations are

GitHub Actions supports special commands that generate annotations visible in the UI:

# An error annotation — a red banner
echo "::error::Deploy failed: health check timeout"

# A warning annotation — a yellow banner  
echo "::warning::Deploy succeeded but latency is high (3.2s)"

# A notice annotation — a blue banner
echo "::notice::Deploy to production completed successfully"

Annotations in a deploy context

- name: Health check with annotations
  run: |
    RESPONSE_TIME=$(curl -sf -w "%{time_total}" -o /dev/null https://your-app.com/health)
    HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" https://your-app.com/health)

    if [ "$HTTP_CODE" != "200" ]; then
      echo "::error title=Deploy Health Check::Health check failed with HTTP $HTTP_CODE"
      exit 1
    fi

    THRESHOLD="3.0"
    SLOW=$(echo "$RESPONSE_TIME > $THRESHOLD" | bc -l 2>/dev/null || echo "0")
    if [ "$SLOW" = "1" ]; then
      echo "::warning title=Slow Response::Health check passed but response time is ${RESPONSE_TIME}s (threshold: ${THRESHOLD}s)"
    else
      echo "::notice title=Deploy OK::Health check passed in ${RESPONSE_TIME}s"
    fi

Annotations with file context

You can associate annotations with specific files:

echo "::error file=docker-compose.yml,line=5::Image tag not found in registry"
echo "::warning file=.github/workflows/deploy.yml,line=42::Consider adding timeout-minutes"

These annotations appear as inline comments on the PR's files, just like code review comments.


The notification pattern for a deploy

The complete pattern

- name: Deploy notification
  if: always()
  env:
    DEPLOY_STATUS: ${{ steps.verify.outcome }}
    ENVIRONMENT: production
    IMAGE_TAG: sha-${{ github.sha }}
    ROLLBACK_TAG: ${{ steps.current.outputs.tag }}
  run: |
    SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
    COMMIT_URL="${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}"
    RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"

    if [ "$DEPLOY_STATUS" = "success" ]; then
      echo "::notice title=Deploy Success::$ENVIRONMENT deployed sha-$SHORT_SHA successfully"

      echo "### ✅ Deploy to $ENVIRONMENT" >> $GITHUB_STEP_SUMMARY
      echo "" >> $GITHUB_STEP_SUMMARY
      echo "**Image:** \`$IMAGE_TAG\`" >> $GITHUB_STEP_SUMMARY
      echo "**Commit:** [$SHORT_SHA]($COMMIT_URL)" >> $GITHUB_STEP_SUMMARY
      echo "**Author:** @${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
      echo "**Time:** $(date -u +'%Y-%m-%d %H:%M UTC')" >> $GITHUB_STEP_SUMMARY

    else
      echo "::error title=Deploy Failed::$ENVIRONMENT deploy failed for sha-$SHORT_SHA — rolled back to $ROLLBACK_TAG"

      echo "### ❌ Deploy to $ENVIRONMENT FAILED" >> $GITHUB_STEP_SUMMARY
      echo "" >> $GITHUB_STEP_SUMMARY
      echo "**Failed image:** \`$IMAGE_TAG\`" >> $GITHUB_STEP_SUMMARY
      echo "**Rolled back to:** \`$ROLLBACK_TAG\`" >> $GITHUB_STEP_SUMMARY
      echo "**Commit:** [$SHORT_SHA]($COMMIT_URL)" >> $GITHUB_STEP_SUMMARY
      echo "**Logs:** [View workflow run]($RUN_URL)" >> $GITHUB_STEP_SUMMARY
      echo "" >> $GITHUB_STEP_SUMMARY
      echo "**Next steps:**" >> $GITHUB_STEP_SUMMARY
      echo "1. Check workflow logs for the failure reason" >> $GITHUB_STEP_SUMMARY
      echo "2. Fix the issue in a new commit" >> $GITHUB_STEP_SUMMARY
      echo "3. Push to main to trigger a new deploy" >> $GITHUB_STEP_SUMMARY
    fi

GitHub's native Notifications

Configuring Actions notifications

GitHub sends automatic notifications for:

  • 📋 Deployment reviews — When a workflow needs approval
  • 📋 Workflow failures — When a workflow fails (configurable)
  • 📋 Workflow successes — When a workflow that previously failed now passes
To configure:
  GitHub → Settings → Notifications → Actions

  ☑ Send notifications for failed workflows only
  ☐ Send notifications for all workflow runs (very noisy)
  
  Or more granular:
  Repo → Settings → Notifications → Custom routing
    → Watch: Workflow runs

Deployment review notifications

When a workflow reaches an approval gate, GitHub automatically sends:

📧 Email: "Deployment review requested: Deploy Pipeline #42"
🔔 GitHub: "Review requested for production deployment"
📱 Mobile: A push notification

These are automatic — you don't need to configure anything in the workflow.
You only need the environment to have required reviewers.

A preview of Module 7's notifications

What comes next

This capsule covers GitHub's native notifications. Module 7 adds external notifications:

Module 7 will add:
  - Slack notifications (a webhook)
  - Deployment status in a team channel
  - Alert on failure with @channel
  - Custom messages with the deploy's details

For now, the native tools are enough: summaries for detailed reports, annotations for visual alerts, deployment status for PRs, and native notifications for email/mobile.

Preparing for Module 7: output as JSON

If you want to prepare your pipeline to integrate external notifications easily later:

- name: Generate deploy event
  id: event
  if: always()
  run: |
    EVENT=$(cat <<'PAYLOAD'
    {
      "environment": "production",
      "status": "${{ steps.verify.outcome }}",
      "image_tag": "sha-${{ github.sha }}",
      "commit_sha": "${{ github.sha }}",
      "commit_url": "${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}",
      "author": "${{ github.actor }}",
      "run_url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
      "timestamp": "$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
    }
    PAYLOAD
    )
    echo "payload=$EVENT" >> $GITHUB_OUTPUT

This JSON can be sent to any webhook in Module 7 without changing the deploy's logic.


Comparisons

Without notifications vs with notifications

AspectWithout notificationsWith notifications
Visibility"Did someone deploy?""Deploy v1.2.3 to production ✅"
Time to awarenessMinutes to hoursImmediate
DebuggingGo to Actions, look for the runA summary with all the info
The teamOnly the person who deployed knowsThe whole team is informed

Job Summary vs annotations vs logs

AspectJob SummaryAnnotationsLogs (echo)
VisibilityThe top of the workflow runBanners in the UIInside the step's log
FormatFull MarkdownSimple textPlain text
PersistencePermanent in the runPermanent in the runPermanent in the run
Ideal useA detailed reportSpecific alertsDebug info

GitHub notifications vs Slack (Module 7)

AspectGitHub nativeSlack/external
SetupZero (included)It requires a webhook/bot
ChannelEmail + mobile + webA specific team channel
CustomizationLimitedTotal (custom messages)
For this guideEnoughModule 7

Troubleshooting

1. The Job Summary doesn't appear in the UI

Symptom: The step runs but there's no summary in the workflow run's tab.

Cause: A syntax error in the Markdown, or the step failed before writing to the summary.

Solution:

- name: Summary
  if: always()    # Important: run it always, even if previous steps failed
  run: |
    echo "### Deploy Report" >> $GITHUB_STEP_SUMMARY
    echo "Status: done" >> $GITHUB_STEP_SUMMARY

Verify that you're using >> (append) and not > (overwrite). If you use >, every step overwrites the previous summary.

2. Annotations don't show as banners

Symptom: echo "::error::message" appears in the logs but not as a banner.

Cause: The command's format is wrong, or there are special characters.

Solution:

echo "::error::Simple message without special characters"

echo "::error title=Deploy Failed::Health check returned HTTP 500"

echo "::error file=docker-compose.yml,line=5,col=1::Invalid image reference"

Don't use double quotes inside the message. If you need variables:

echo "::error title=Deploy Failed::Health check failed for $IMAGE_TAG"

3. The deployment status doesn't appear on the PR

Symptom: The job runs but the PR doesn't show the deployment status.

Cause: The job doesn't declare environment:, or the PR isn't associated with the commit that triggered the deploy.

Solution: Verify that:

deploy:
  environment:
    name: staging           # Necessary for deployment tracking
    url: https://staging.your-app.com  # Optional, but it generates the "View deployment" link

The deployment status only appears on PRs if the commit that triggered the deploy is on the PR's branch.

4. The review notifications don't arrive

Symptom: The workflow is waiting for review but the reviewer doesn't get a notification.

Cause: GitHub's notifications are disabled for the reviewer.

Solution:

The reviewer must go to:
  GitHub → Settings → Notifications → Actions
  ☑ Send notifications for workflow runs requiring my review

  And also:
  GitHub → Settings → Notifications → Default notification email
  Verify that the email is correct

Exercises

Exercise 1: Create a multi-stage Job Summary

Create a step that generates a Job Summary with a table format showing the result of each pipeline stage: test, build, staging, production. Use variables from the previous steps.

See solution
- name: Pipeline summary
  if: always()
  run: |
    echo "### Pipeline Report — $(date -u +'%Y-%m-%d %H:%M UTC')" >> $GITHUB_STEP_SUMMARY
    echo "" >> $GITHUB_STEP_SUMMARY
    echo "| Stage | Status | Details |" >> $GITHUB_STEP_SUMMARY
    echo "|-------|--------|---------|" >> $GITHUB_STEP_SUMMARY
    echo "| Tests | ${{ needs.test.result == 'success' && '✅' || '❌' }} | Lint + pytest |" >> $GITHUB_STEP_SUMMARY
    echo "| Build | ${{ needs.build.result == 'success' && '✅' || '❌' }} | Docker image |" >> $GITHUB_STEP_SUMMARY
    echo "| Staging | ${{ needs.deploy-staging.result == 'success' && '✅' || '❌' }} | [staging.your-app.com](https://staging.your-app.com) |" >> $GITHUB_STEP_SUMMARY
    echo "| Production | ${{ steps.verify.outcome == 'success' && '✅' || '❌' }} | [your-app.com](https://your-app.com) |" >> $GITHUB_STEP_SUMMARY
    echo "" >> $GITHUB_STEP_SUMMARY
    echo "**Commit:** [\`$(echo ${{ github.sha }} | cut -c1-7)\`](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }})" >> $GITHUB_STEP_SUMMARY
    echo "**Author:** @${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
    echo "**Run:** [#${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY

Note: To access needs.*.result from a job, you need to declare those jobs in needs::

deploy-production:
  needs: [test, build, deploy-staging]

Exercise 2: Conditional annotations by latency

Create a step that measures the health check's response time and generates: ::notice if < 1s, ::warning if 1-3s, ::error if > 3s.

See solution
- name: Health check with latency annotation
  run: |
    RESPONSE_TIME=$(curl -sf -w "%{time_total}" -o /dev/null https://your-app.com/health 2>/dev/null || echo "999")

    if [ "$RESPONSE_TIME" = "999" ]; then
      echo "::error title=Health Check Failed::Could not connect to https://your-app.com/health"
      exit 1
    fi

    SECONDS_INT=$(echo "$RESPONSE_TIME" | cut -d. -f1)
    MS=$(echo "$RESPONSE_TIME * 1000" | bc | cut -d. -f1)

    if [ "$SECONDS_INT" -lt 1 ]; then
      echo "::notice title=Health OK::Response time: ${MS}ms (excellent)"
    elif [ "$SECONDS_INT" -lt 3 ]; then
      echo "::warning title=Slow Response::Response time: ${MS}ms (consider optimizing)"
    else
      echo "::error title=Very Slow Response::Response time: ${MS}ms (exceeds 3s threshold)"
    fi

    echo "Health check: HTTP 200, ${MS}ms" >> $GITHUB_STEP_SUMMARY

Key points:

  • curl -w "%{time_total}" measures the request's total time
  • -o /dev/null discards the body, we only want the time
  • bc does decimal arithmetic in bash
  • The annotations appear as banners in the Actions UI

Exercise 3: Prepare the JSON payload for external notifications

Create a step that generates a JSON with all the deploy's information (environment, status, image, commit, author, timestamp) and saves it as an output for future use (Module 7).

See solution
- name: Generate notification payload
  id: notification
  if: always()
  run: |
    STATUS="${{ steps.verify.outcome }}"
    SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)

    PAYLOAD=$(python3 -c "
    import json, datetime
    data = {
        'pipeline': 'deploy',
        'environment': 'production',
        'status': '${STATUS}',
        'image_tag': 'sha-${{ github.sha }}',
        'commit': {
            'sha': '${{ github.sha }}',
            'short': '${SHORT_SHA}',
            'url': '${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}',
            'message': '''${{ github.event.head_commit.message }}'''[:100]
        },
        'author': '${{ github.actor }}',
        'run': {
            'id': '${{ github.run_id }}',
            'number': ${{ github.run_number }},
            'url': '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
        },
        'timestamp': datetime.datetime.utcnow().isoformat() + 'Z'
    }
    if '${STATUS}' == 'failure':
        data['rollback_to'] = '${{ steps.current.outputs.tag }}'
    print(json.dumps(data))
    ")

    echo "payload<<EOF" >> $GITHUB_OUTPUT
    echo "$PAYLOAD" >> $GITHUB_OUTPUT
    echo "EOF" >> $GITHUB_OUTPUT

    echo "Notification payload generated:"
    echo "$PAYLOAD" | python3 -m json.tool

This payload can be used in Module 7:

- name: Send Slack notification
  if: always()
  run: |
    curl -X POST "${{ secrets.SLACK_WEBHOOK }}" \
      -H "Content-Type: application/json" \
      -d '${{ steps.notification.outputs.payload }}'

Exercise 4: A summary with the reviewer's checklist

Create a Job Summary for the staging job that includes an interactive checklist for the reviewer, with the verifications they must do before approving the deploy to production.

See solution
- name: Staging review checklist
  run: |
    echo "### 🔍 Staging Deploy Complete — Review Required" >> $GITHUB_STEP_SUMMARY
    echo "" >> $GITHUB_STEP_SUMMARY
    echo "| | Detail |" >> $GITHUB_STEP_SUMMARY
    echo "|---|--------|" >> $GITHUB_STEP_SUMMARY
    echo "| **Image** | \`sha-$(echo ${{ github.sha }} | cut -c1-7)\` |" >> $GITHUB_STEP_SUMMARY
    echo "| **Staging URL** | [staging.your-app.com](https://staging.your-app.com) |" >> $GITHUB_STEP_SUMMARY
    echo "| **Commit** | [\`$(echo ${{ github.sha }} | cut -c1-7)\`](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}) — ${{ github.event.head_commit.message }} |" >> $GITHUB_STEP_SUMMARY
    echo "| **Smoke tests** | ✅ Passed |" >> $GITHUB_STEP_SUMMARY
    echo "" >> $GITHUB_STEP_SUMMARY
    echo "### Reviewer Checklist" >> $GITHUB_STEP_SUMMARY
    echo "" >> $GITHUB_STEP_SUMMARY
    echo "Before approving production deploy, verify:" >> $GITHUB_STEP_SUMMARY
    echo "" >> $GITHUB_STEP_SUMMARY
    echo "- [ ] Open [staging.your-app.com](https://staging.your-app.com) and verify it loads" >> $GITHUB_STEP_SUMMARY
    echo "- [ ] Test \`/health\` endpoint returns 200" >> $GITHUB_STEP_SUMMARY
    echo "- [ ] Test \`/chat\` endpoint with a sample message" >> $GITHUB_STEP_SUMMARY
    echo "- [ ] Verify response quality is acceptable" >> $GITHUB_STEP_SUMMARY
    echo "- [ ] Check response latency < 5 seconds" >> $GITHUB_STEP_SUMMARY
    echo "- [ ] Review commit changes in the PR" >> $GITHUB_STEP_SUMMARY
    echo "" >> $GITHUB_STEP_SUMMARY
    echo "**Approve the deployment when all checks pass.**" >> $GITHUB_STEP_SUMMARY

The checkboxes aren't interactive in GitHub Summaries (it's static Markdown), but they serve as a visual reference for the reviewer.


Summary

  • Job Summaries ($GITHUB_STEP_SUMMARY) generate Markdown reports visible in the Actions UI
  • Deployment Status appears automatically on PRs when you use environment: in the job
  • Annotations (::error::, ::warning::, ::notice::) generate visual banners for alerts
  • GitHub Notifications send email/mobile automatically for deployment reviews
  • The notification pattern includes: status, image tag, commit, author, timestamp, URL
  • Always use if: always() on notification steps so they run even if the deploy fails
  • Prepare a JSON payload to make the integration with Slack/external services in Module 7 easier
  • Progressive levels: summaries → annotations → GitHub native → Slack (Module 7)

Additional resources

  1. GitHub Actions — Job summaries — Official documentation on $GITHUB_STEP_SUMMARY
  2. GitHub Actions — Workflow commands — Annotations and other commands
  3. GitHub — Deployment status — The deployment history
  4. GitHub Notifications settings — Configuring notifications
  5. GitHub Actions — Contexts — Variables available for notifications (github.sha, github.actor, etc.)
  6. Markdown syntax for GitHub — Markdown format for summaries