Every pull request that touches an email flow should verify that flow end-to-end before it merges. Here’s how to wire MailFork into GitHub Actions to do exactly that — with isolated inboxes per run and automatic cleanup.
Prerequisites
- A MailFork account and API key (sign up here)
- A team inbox already created (e.g.
[email protected]) @mailfork/sdkinstalled in your project
Step 1 — Add your API key as a secret
In your GitHub repository: Settings → Secrets and variables → Actions → New repository secret
MAILFORK_API_KEY=mf_live_...
Step 2 — Install the SDK in your test environment
npm install @mailfork/sdk
Step 3 — Set up the MailFork client in your test suite
import { MailForkClient } from '@mailfork/sdk';
const mf = new MailForkClient({ apiKey: process.env.MAILFORK_API_KEY! });
Step 4 — Create a per-run alias
Aliases prevent test runs from seeing each other’s emails. Each alias is a unique address on your shared inbox; it expires automatically after the TTL.
let alias: Awaited<ReturnType<typeof mf.inboxes.createAlias>>;
beforeAll(async () => {
alias = await mf.inboxes.createAlias({
inbox: '[email protected]',
ttlHours: 1,
onExpire: 'delete',
});
});
Step 5 — Write the email test
it('sends a password reset email and the link works', async () => {
// trigger the flow using the alias address as the user's email
await request(app)
.post('/auth/forgot-password')
.send({ email: alias.address })
.expect(200);
// wait for delivery (default timeout: 10s)
const email = await mf.inboxes.waitForEmail({
to: alias.address,
subject: /reset your password/i,
timeout: 10_000,
});
expect(email).toBeTruthy();
// extract the reset link from the email body
const match = email.body.html?.match(/href="(https:\/\/[^"]+\/reset-password[^"]*)"/);
expect(match).toBeTruthy();
const resetLink = new URL(match![1]);
const token = resetLink.searchParams.get('token');
// follow the link
await request(app)
.post('/auth/reset-password')
.send({ token, password: 'new-password-123' })
.expect(200);
});
Step 6 — Add the workflow step
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
env:
MAILFORK_API_KEY: ${{ secrets.MAILFORK_API_KEY }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm test
That’s it. Every run creates a fresh alias, uses it for the email flow, and cleans up after itself. No shared state between runs. No flaky tests from leftover emails.
Tips for robust email tests
Use waitForEmail with a timeout — don’t poll manually. The SDK handles backoff and timeout for you.
Match on subject, not just recipient — if your app sends multiple emails (welcome + verification), match on a subject pattern to pick the right one.
Check the plain-text body too — some email clients display plain text when HTML rendering fails. Assert that your plain-text version has the link.
Test attachment names and sizes — if your flow attaches a PDF receipt, verify email.attachments[0].filename and check it’s non-zero bytes.