Jest recipe
This pattern is useful when your service-under-test sends real SMTP — point
its MAIL_FROM/transport at MailFade, and assert on what shows up.
Setup
// tests/helpers/mailfade.ts
const API = process.env.MAILFADE_API_URL ?? "https://api.mailfade.dev";
const KEY = process.env.MAILFADE_KEY;
const headers: Record<string, string> = KEY ? { Authorization: `Bearer ${KEY}` } : {};
export const freshInbox = (prefix = "jest") =>
`${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}@mailfade.dev`;
export async function waitForEmail(
inbox: string,
predicate: (msg: any) => boolean = () => true,
timeoutMs = 30_000,
): Promise<any> {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const r = await fetch(`${API}/inbox/${encodeURIComponent(inbox)}`, { headers });
const { emails = [] } = await r.json();
const hit = emails.find(predicate);
if (hit) {
const full = await fetch(`${API}/message/${hit.id}`, { headers });
return full.json();
}
await new Promise((res) => setTimeout(res, 1000));
}
throw new Error(`no matching email at ${inbox}`);
}
Test
import { freshInbox, waitForEmail } from "./helpers/mailfade";
import { signupUser } from "../src/users";
test("signup sends a verification email", async () => {
const inbox = freshInbox();
await signupUser({ email: inbox, password: "hunter2hunter2" });
const email = await waitForEmail(inbox, (m) => /confirm/i.test(m.subject ?? ""));
expect(email.sender).toMatch(/@acme\.com$/);
expect(email.text).toContain("https://");
});
CI
- run: npx jest
env:
MAILFADE_KEY: ${{ secrets.MAILFADE_KEY }}