← Back to documentation

Sending GitHub Actions Emails and Reports

GitHub Actions workflow examples that send build status, test reports, and deployment summaries to email, Slack, or any configured target.

9 min read

Use PayloadRelay to send email notifications from GitHub Actions without configuring SMTP, managing secrets for third-party email APIs, or adding complex action dependencies.

Purpose

This guide helps you:

  • Add a PayloadRelay notification step to any GitHub Actions workflow.
  • Send build pass/fail notifications with context.
  • Forward test report summaries.
  • Include workflow metadata (repo, branch, commit, actor).

Prerequisites and permissions

  • A PayloadRelay endpoint configured to accept POST with JSON payload format.
  • A confirmed email target (or Slack/webhook target) attached to the endpoint.
  • The endpoint URL stored as a GitHub Actions secret.

Step-by-step workflow

1. Store the endpoint URL as a secret

  1. In your GitHub repository, go to SettingsSecrets and variablesActions.
  2. Create a new repository secret:
    • Name: PAYLOADRELAY_ENDPOINT
    • Value: https://api.payloadrelay.com/relay/YOUR_ENDPOINT_ID

If the endpoint requires authentication, store the token as a separate secret:

  • Name: PAYLOADRELAY_TOKEN
  • Value: your Bearer token or API key

2. Basic build notification

Add a notification step at the end of your workflow:

Code Example
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build
        run: npm ci && npm run build

      - name: Run tests
        run: npm test

      - name: Notify via PayloadRelay
        if: always()
        run: |
          curl -s -X POST "${{ secrets.PAYLOADRELAY_ENDPOINT }}" \
            -H "Content-Type: application/json" \
            -d '{
              "subject": "CI: ${{ job.status }} — ${{ github.repository }}",
              "repository": "${{ github.repository }}",
              "branch": "${{ github.ref_name }}",
              "commit": "${{ github.sha }}",
              "actor": "${{ github.actor }}",
              "status": "${{ job.status }}",
              "run_url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
              "event": "${{ github.event_name }}"
            }'

The if: always() ensures the notification fires regardless of whether previous steps passed or failed.

3. Build notification with authentication

If your endpoint requires a Bearer token:

Code Example
      - name: Notify via PayloadRelay
        if: always()
        run: |
          curl -s -X POST "${{ secrets.PAYLOADRELAY_ENDPOINT }}" \
            -H "Content-Type: application/json" \
            -H "Authorization: Bearer ${{ secrets.PAYLOADRELAY_TOKEN }}" \
            -d '{
              "subject": "CI: ${{ job.status }} — ${{ github.repository }}",
              "repository": "${{ github.repository }}",
              "branch": "${{ github.ref_name }}",
              "commit": "${{ github.sha }}",
              "actor": "${{ github.actor }}",
              "status": "${{ job.status }}",
              "run_url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
            }'

4. Test report summary

Capture test output and include a summary in the notification:

Code Example
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install dependencies
        run: npm ci

      - name: Run tests and capture output
        id: tests
        run: |
          set +e
          TEST_OUTPUT=$(npm test 2>&1)
          TEST_EXIT=$?
          set -e

          SUMMARY=$(echo "$TEST_OUTPUT" | tail -5)

          echo "exit_code=$TEST_EXIT" >> "$GITHUB_OUTPUT"
          {
            echo "summary<<SUMMARY_EOF"
            echo "$SUMMARY"
            echo "SUMMARY_EOF"
          } >> "$GITHUB_OUTPUT"

      - name: Send test report
        if: always()
        env:
          TEST_SUMMARY: ${{ steps.tests.outputs.summary }}
        run: |
          STATUS="passed"
          if [ "${{ steps.tests.outputs.exit_code }}" != "0" ]; then
            STATUS="failed"
          fi

          jq -n \
            --arg subject "Tests $STATUS — ${{ github.repository }}" \
            --arg repo "${{ github.repository }}" \
            --arg branch "${{ github.ref_name }}" \
            --arg commit "${{ github.sha }}" \
            --arg actor "${{ github.actor }}" \
            --arg status "$STATUS" \
            --arg summary "$TEST_SUMMARY" \
            --arg run_url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
            '{
              subject: $subject,
              repository: $repo,
              branch: $branch,
              commit: $commit,
              actor: $actor,
              test_status: $status,
              test_summary: $summary,
              run_url: $run_url
            }' | curl -s -X POST "${{ secrets.PAYLOADRELAY_ENDPOINT }}" \
              -H "Content-Type: application/json" \
              -d @-

Using jq for JSON construction ensures special characters in test output are properly escaped.

5. Deployment notification

Notify your team after a successful deployment:

Code Example
name: Deploy

on:
  push:
    tags:
      - "v*"

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Deploy
        run: ./deploy.sh

      - name: Notify deployment
        if: success()
        run: |
          curl -s -X POST "${{ secrets.PAYLOADRELAY_ENDPOINT }}" \
            -H "Content-Type: application/json" \
            -d '{
              "subject": "Deployed ${{ github.ref_name }} — ${{ github.repository }}",
              "repository": "${{ github.repository }}",
              "tag": "${{ github.ref_name }}",
              "commit": "${{ github.sha }}",
              "actor": "${{ github.actor }}",
              "event": "deployment",
              "run_url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
            }'

      - name: Notify deployment failure
        if: failure()
        run: |
          curl -s -X POST "${{ secrets.PAYLOADRELAY_ENDPOINT }}" \
            -H "Content-Type: application/json" \
            -d '{
              "subject": "⚠ Deploy FAILED: ${{ github.ref_name }} — ${{ github.repository }}",
              "repository": "${{ github.repository }}",
              "tag": "${{ github.ref_name }}",
              "commit": "${{ github.sha }}",
              "actor": "${{ github.actor }}",
              "event": "deployment_failed",
              "run_url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
            }'

6. Scheduled health check report

Run a scheduled workflow that reports system health:

Code Example
name: Health Check

on:
  schedule:
    - cron: "0 9 * * 1-5" # weekdays at 09:00 UTC

jobs:
  health:
    runs-on: ubuntu-latest
    steps:
      - name: Check services
        id: health
        run: |
          API_STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://api.example.com/health)
          WEB_STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://example.com)

          echo "api_status=$API_STATUS" >> "$GITHUB_OUTPUT"
          echo "web_status=$WEB_STATUS" >> "$GITHUB_OUTPUT"

      - name: Send health report
        run: |
          curl -s -X POST "${{ secrets.PAYLOADRELAY_ENDPOINT }}" \
            -H "Content-Type: application/json" \
            -d '{
              "subject": "Daily Health Check — ${{ github.repository }}",
              "api_status": "${{ steps.health.outputs.api_status }}",
              "web_status": "${{ steps.health.outputs.web_status }}",
              "timestamp": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",
              "run_url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
            }'

Expected result and verification checks

  • Workflow notification steps complete without errors.
  • Requests appear in PayloadRelay Request activity with outcome ACCEPTED.
  • Email (or other target) receives the payload with workflow context.
  • Failed builds trigger notifications due to if: always().

Common issues and fixes

  • Secret not found: verify secret names match exactly between workflow YAML and repository settings.
  • 401 Unauthorized: confirm the endpoint auth type and that PAYLOADRELAY_TOKEN is correct.
  • Empty fields in payload: use ${{ }} syntax for GitHub context variables, not shell variables.
  • JSON parse errors: use jq to construct payloads that contain dynamic text with special characters.
  • Notifications not sent on failure: ensure if: always() is set on the notification step.

Related guides