MailFlatDocs
Documentation/Test frameworks & CI/GitHub Actions

Email testing in GitHub Actions

Give every workflow run its own inbox, pass the address to the suite as an environment variable, and sweep it up even when the job fails.

How it fits together

The pattern is the same as locally, with two CI-specific details: the key comes from repository secrets, and teardown runs under if: always() because that is exactly when inboxes get left behind.

The workflow

YAML
# .github/workflows/e2e.yml
name: e2e
on: [push]
 
jobs:
test:
runs-on: ubuntu-latest
env:
MAILFLAT_API_KEY: ${{ secrets.MAILFLAT_API_KEY }}
MAILFLAT_API: https://mailflat.net/api/v1
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
 
- name: Open an inbox for this run
run: |
ADDRESS=$(curl -s -X POST "$MAILFLAT_API/inboxes" \
-H "X-API-Key: $MAILFLAT_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"prefix\":\"ci-${GITHUB_RUN_ID}\",\"retention_hours\":2}" | jq -r .address)
echo "TEST_EMAIL=$ADDRESS" >> "$GITHUB_ENV"
 
- run: npm ci
- run: npx playwright test
 
- name: Tear down
if: always()
run: |
curl -s -X DELETE "$MAILFLAT_API/inboxes/$TEST_EMAIL" \
-H "X-API-Key: $MAILFLAT_API_KEY" || true

Or let the suite do it

If your tests already create an inbox per test, the workflow only needs the key.
YAML
- run: npx playwright test
env:
MAILFLAT_API_KEY: ${{ secrets.MAILFLAT_API_KEY }}

Worth knowing

retention_hours is the safety net: even if the teardown step never runs, the mail expires on its own.
An account key printed into a public build log grants access to every inbox you own — keep it in secrets.