Guides
Test verification emails with Cypress
Drive email verification flows from Cypress specs with cy.task and the SDK.Cypress runs spec code inside the browser, so the Node SDK cannot be imported in a spec file.
Register a few cy.task handlers in setupNodeEvents instead: the tasks own the SDK in the
config's Node process, and specs call them with plain values.
Start the services
Start InboxTap and the application before the Cypress run, locally and in CI:
npx inboxtap &
npm run dev &
npx cypress runConfigure the application under test to send SMTP to localhost:1025.
Register the tasks
// cypress.config.ts
import { defineConfig } from "cypress";
import { InboxTapClient, TestInbox } from "inboxtap/client";
const inboxTap = new InboxTapClient();
interface WaitArgs {
to: string;
subject?: string;
contains?: string;
pattern?: string;
timeoutMs?: number;
}
export default defineConfig({
e2e: {
setupNodeEvents(on) {
on("task", {
"inboxtap:createInbox": async (alias: string) =>
(await inboxTap.createInbox({ alias })).address,
"inboxtap:waitForLink": ({ to, ...options }: WaitArgs) =>
new TestInbox(inboxTap, to).waitForLink(options),
"inboxtap:waitForCode": ({ to, ...options }: WaitArgs) =>
new TestInbox(inboxTap, to).waitForCode(options),
});
},
},
});The create task returns the inbox's address as a string, and each wait task rebuilds a TestInbox
from the address it receives.
Write the test
describe("sign up", () => {
it("verifies a new account", () => {
cy.task<string>("inboxtap:createInbox", "signup").then((address) => {
cy.visit("/signup");
cy.get("input[name=email]").type(address);
cy.contains("button", "Create account").click();
cy.task<string>(
"inboxtap:waitForLink",
{ to: address, subject: "verify your email", contains: "/verify", timeoutMs: 20_000 },
{ timeout: 30_000 },
).then((url) => {
cy.visit(url);
cy.contains("Email verified").should("be.visible");
});
});
});
});Give the Cypress timeout option more time than timeoutMs, so a slow email fails inside the task
with InboxTap's error message rather than as a generic task timeout. Use the inboxtap:waitForCode
task when the application asks for an OTP instead of a link.
Parallel isolation
Create the inbox inside each test, never once for the whole suite. Every task call generates a fresh recipient address, so parallel specs can share one InboxTap server without reading one another's messages. The bounded store evicts old messages on its own; add a clear task only if immediate cleanup helps local debugging.
Direct HTTP access
cy.request executes from Cypress's Node-side proxy, so it can reach the loopback API directly with
no task registration:
cy.request(
"http://localhost:8025/api/emails/wait?to=signup-1@local.test&timeoutMs=20000",
)
.its("body.email.links")
.should("not.be.empty");This trades away the SDK's multi-message scanning and value extraction, so prefer the tasks for anything beyond a one-off check. The full endpoint list is in the HTTP API reference.