Skip to content

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 }}