Mailwurf

Vitest and Node

Test the emails your backend sends, without a browser.

@mailwurf/e2e only needs fetch. Any Node test runner can use it to check that an endpoint sends the right email.

tests/password-reset.test.ts
import { expect, it } from "vitest";
import { createMailwurf } from "@mailwurf/e2e";

const mailwurf = createMailwurf(process.env.MAILWURF_API_KEY!); // API key (mw_live_…) from Dashboard → API keys
const appUrl = process.env.APP_URL ?? "http://localhost:3000";

it("sends a password reset link", { timeout: 30_000 }, async () => {
  const inbox = await mailwurf.createInbox();

  await fetch(`${appUrl}/api/signup`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ email: inbox.address, password: "correct-horse-battery" }),
  });
  const welcome = await inbox.waitFor({ subject: "Welcome" });

  const res = await fetch(`${appUrl}/api/password-reset`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ email: inbox.address }),
  });
  expect(res.status).toBe(202);

  const mail = await inbox.waitFor({ subject: "Reset", after: welcome.id });
  expect(mail.links.some((url) => url.startsWith(`${appUrl}/reset-password?token=`))).toBe(true);
});

Set the test timeout above the Mailwurf timeout, so a missing email fails with MailwurfTimeoutError instead of a runner timeout.

Without the package

The API is plain HTTP. From any language:

INBOX=$(curl -s -X POST "https://api.mailwurf.io/v1/inboxes" \
  -H "Authorization: Bearer $MAILWURF_API_KEY")
ID=$(echo "$INBOX" | jq -r .id)
ADDRESS=$(echo "$INBOX" | jq -r .address)

# trigger the email for $ADDRESS, then:
curl -s "https://api.mailwurf.io/v1/inboxes/$ID/messages/wait?subject=Reset&timeout=30000" \
  -H "Authorization: Bearer $MAILWURF_API_KEY" | jq -r '.links[]'

See HTTP API.

On this page