Sending GitHub Actions Emails and Reports
Send workflow results to email, Slack, or another configured target.
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
POSTwith theJSONpayload 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#
- In the GitHub repository, open
Settings. SelectSecrets and variables, thenActions. - Create a new repository secret:
- Name:
PAYLOADRELAY_ENDPOINT - Value:
https://api.payloadrelay.com/relay/YOUR_ENDPOINT_ID
- Name:
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:
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:
- 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:
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:
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:
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 activityasCompleted(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 thePAYLOADRELAY_TOKENvalue 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
jqto 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.