Configuring the Litmus API in GitHub Actions for Automated Email QA
Integrating Litmus API into GitHub Actions eliminates manual cross-client rendering checks and enforces strict visual standards in modern transactional pipelines. This guide provides the exact authentication headers, payload structures, and CI/CD workflow syntax required to trigger automated Litmus tests on every pull request. Before implementing the configuration, align this setup with established Email Testing & QA Workflows to maintain deliverability baselines and prevent regression.
Prerequisites and API Authentication Setup
- Enable API Access: Log into your Litmus dashboard. Navigate to
Account Settings > API Accessand generate a Personal Access Token (PAT). - Secure Storage: Add the token to GitHub:
Repository Settings > Secrets and variables > Actions > New repository secret.
- Name:
LITMUS_API_KEY - Value:
[your_litmus_pat]
- Auth Standard: The Litmus REST API requires
Authorization: Bearer <token>for all endpoints. Never hardcode credentials or commit tokens to version control. GitHub Actions injects secrets at runtime and masks them in runner logs.
Constructing the GitHub Actions Workflow YAML
Create .github/workflows/email-validation.yml at the repository root. The configuration below compiles your email template, authenticates securely, and posts the payload to the Litmus /v3/tests endpoint.
name: Email Validation Pipeline
on:
push:
branches: [main]
pull_request:
branches: [develop]
permissions:
contents: read
checks: write
jobs:
litmus-qa:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
# Replace with your actual template compilation command
- name: Build Email HTML
run: |
npm ci
npm run build:email
# Read compiled HTML into environment variable
echo "HTML_PAYLOAD=$(cat dist/email.html | tr -d '\n' | sed 's/"/\\"/g')" >> $GITHUB_ENV
- name: Trigger Litmus API Test
id: trigger
run: |
set -e
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST https://api.litmus.com/v3/tests \
-H "Authorization: Bearer ${{ secrets.LITMUS_API_KEY }}" \
-H "Content-Type: application/json" \
-d "{
\"test\": {
\"subject\": \"Automated QA Check\",
\"from_email_address\": \"qa@yourdomain.com\",
\"email_client_ids\": [1, 2, 3, 4, 5],
\"html_source\": \"${{ env.HTML_PAYLOAD }}\"
}
}")
HTTP_CODE=$(echo "$RESPONSE" | tail -n 1)
BODY=$(echo "$RESPONSE" | sed '$d')
if [ "$HTTP_CODE" -ne 201 ]; then
echo "::error::Litmus API rejected request. HTTP $HTTP_CODE"
echo "$BODY"
exit 1
fi
TEST_ID=$(echo "$BODY" | jq -r '.id')
echo "test_id=$TEST_ID" >> $GITHUB_OUTPUT
Handling Asynchronous Webhook Callbacks
Litmus processes cross-client rendering asynchronously. Implement a polling loop with exponential backoff to query /v3/tests/{id} until completion. Parse the rendering_results array and fail the GitHub check if critical parsing errors are detected.
- name: Poll Rendering Status
run: |
MAX_RETRIES=25
RETRY_COUNT=0
DELAY=10
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
STATUS_RESPONSE=$(curl -s -H "Authorization: Bearer ${{ secrets.LITMUS_API_KEY }}" \
"https://api.litmus.com/v3/tests/${{ steps.trigger.outputs.test_id }}")
STATUS=$(echo "$STATUS_RESPONSE" | jq -r '.status')
if [ "$STATUS" = "completed" ]; then
echo "Rendering complete."
break
elif [ "$STATUS" = "failed" ]; then
echo "::error::Litmus processing failed. Check payload validity."
exit 1
fi
echo "Status: $STATUS. Waiting ${DELAY}s before retry..."
sleep $DELAY
DELAY=$((DELAY * 2))
RETRY_COUNT=$((RETRY_COUNT + 1))
done
if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then
echo "::error::Polling timeout reached. Verify API health."
exit 1
fi
- name: Validate Rendering Results
run: |
RESULTS=$(curl -s -H "Authorization: Bearer ${{ secrets.LITMUS_API_KEY }}" \
"https://api.litmus.com/v3/tests/${{ steps.trigger.outputs.test_id }}/results")
# Fail pipeline if critical rendering errors exceed threshold
CRITICAL_ERRORS=$(echo "$RESULTS" | jq '[.rendering_results[] | select(.status == "error")] | length')
if [ "$CRITICAL_ERRORS" -gt 0 ]; then
echo "::error::Found $CRITICAL_ERRORS critical rendering errors. Review Litmus dashboard."
exit 1
fi
echo "All rendering checks passed."
This automated gatekeeping is a cornerstone of robust Litmus & Email on Acid Workflows and ensures broken templates never reach production environments.
Troubleshooting Common Configuration Errors
| HTTP Status | Root Cause | Resolution |
|---|---|---|
401 Unauthorized |
Expired PAT, incorrect scope, or IP allowlist blocking runner IPs. | Regenerate token. Verify LITMUS_API_KEY secret length matches dashboard. Disable IP allowlisting for GitHub runner ranges if applicable. |
400 Bad Request |
Malformed JSON, missing email_client_ids, or HTML payload > 5MB. |
Validate payload with jq . before POST. Strip inline styles exceeding size limits. Ensure Content-Type: application/json is explicitly set. |
429 Too Many Requests |
Exceeding Litmus API rate limits during parallel PR checks. | Implement exponential backoff (as configured). Serialize workflow runs using concurrency: group: email-qa-${{ github.ref }} in YAML. |
jq: command not found |
Missing JSON parser in runner environment. | Add sudo apt-get install -y jq to workflow steps or use actions/github-script with Node.js JSON.parse(). |
Debugging Protocol:
- Set repository variable
ACTIONS_RUNNER_DEBUG=trueto enable verbose runner logs. - Add
-vtocurlcommands to inspect exact request/response headers. - Verify workflow permissions:
permissions: checks: writeis mandatory for GitHub Check API integration.
Production Testing & Validation Steps
- Dry Run Locally: Use
actor GitHub CLIgh workflow run email-validation.yml --ref <branch>to trigger without merging. - Secret Masking Verification: Add a debug step
echo "::add-mask::${{ secrets.LITMUS_API_KEY }}"to confirm GitHub masks the token in logs. - Failure Path Testing: Intentionally inject malformed HTML (e.g., unclosed
<table>tags) into your template. Verify the workflow exits with code1and posts a failed check status to the PR. - Success Validation: Merge a clean PR. Confirm the GitHub Checks tab shows
Email Validation Pipelineas green and links to the Litmus test report.
Integrating Litmus API into GitHub Actions standardizes visual QA across distributed teams. By enforcing strict payload validation, implementing exponential backoff, and gating merges on rendering results, you eliminate manual review overhead and guarantee production-ready email templates.