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.
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
POSTwithJSONpayload 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
- In your GitHub repository, go to
Settings→Secrets and variables→Actions. - Create a new repository secret:
- Name:
PAYLOADRELAY_ENDPOINT - Value:
https://api.payloadrelay.com/relay/YOUR_ENDPOINT_ID
- Name:
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:
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:
- 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:
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:
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:
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 activitywith outcomeACCEPTED. - 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 thatPAYLOADRELAY_TOKENis correct.- Empty fields in payload: use
${{ }}syntax for GitHub context variables, not shell variables. - JSON parse errors: use
jqto construct payloads that contain dynamic text with special characters. - Notifications not sent on failure: ensure
if: always()is set on the notification step.