← Back to documentation

Sending GitHub Actions Emails and Reports

Send workflow results to email, Slack, or another configured target.

2 min read

Use PayloadRelay to send a notification from GitHub Actions. You do not configure SMTP, manage the secrets of a third-party email API, or add a complex action dependency.

Purpose#

Use this guide to:

  • Add a PayloadRelay notification step to a GitHub Actions workflow.
  • Send a build success notification and a build failure notification with the context.
  • Send a test report summary.
  • Add workflow data such as the repository, the branch, the commit, and the actor.

Before you start#

  • Create a PayloadRelay endpoint that accepts POST with the JSON payload format.
  • Attach a confirmed email, Slack, or webhook target to the endpoint.
  • Store the endpoint URL as a GitHub Actions secret.

Procedure#

1. Store the endpoint URL as a secret#

  1. In the GitHub repository, open Settings. Select Secrets and variables, then Actions.
  2. Create a new repository secret:
    • Name: PAYLOADRELAY_ENDPOINT
    • Value: https://api.payloadrelay.com/relay/YOUR_ENDPOINT_ID

If the endpoint needs 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() value sends the notification if the earlier steps pass or fail.

3. Build notification with authentication#

If the endpoint needs a Bearer token, use this step:

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 the test output, and put 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 @-

The jq command escapes the special characters in the test output when it builds the JSON.

5. Deployment notification#

Send a notification 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#

Use a scheduled workflow to send a system health report:

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#

  • The workflow notification steps complete with no error.
  • A request appears in PayloadRelay Request activity as Completed (ACCEPTED).
  • The email target, or a different target, receives the payload with the workflow context.
  • A failed build sends a notification, because if: always() is set.

Common issues#

  • PayloadRelay cannot find the secret: make sure that the secret names agree in the workflow YAML and in the repository configuration.
  • 401 Unauthorized: make sure that the endpoint authentication type and the PAYLOADRELAY_TOKEN value are correct.
  • Empty fields in the payload: use the ${{ }} syntax for a GitHub context variable. Do not use a shell variable for these values.
  • A JSON parse error: use jq to build JSON that contains dynamic text with special characters.
  • No notification after a failure: make sure that if: always() is set on the notification step.