Cypress

Test emails in Cypress

Your Cypress spec signs up with a fresh {teamId}.signup-…@zyntramail.com address, reads the email with cy.request, and visits the link in it. No plugin, no Gmail login, no mail catcher to run next to your app.

cypress/e2e/signup.cy.js
// cypress/e2e/signup.cy.js
describe("sign-up", () => {
  it("confirms the email address", () => {
    const team = Cypress.env("ZYNTRA_TEAM_ID");
    const email = `${team}.signup-${Date.now()}@zyntramail.com`;

    cy.visit("/signup");
    cy.get("input[name=email]").type(email);
    cy.get("input[name=password]").type("a-long-test-password");
    cy.contains("button", "Create account").click();

    // One request: waits up to 30 s on the server for the email.
    cy.request({
      url: "https://api.zyntra.app/emails/last",
      qs: { email },
      headers: { "X-API-Key": Cypress.env("ZYNTRA_API_KEY") },
      timeout: 45000,
    }).then(({ body }) => {
      expect(body.subject).to.contain("Confirm");
      const confirm = body.links.find((l) => l.url.includes("/verify"));
      cy.visit(confirm.url);
    });

    cy.contains("Email confirmed").should("be.visible");
  });
});
CI environment
# CI: Cypress reads CYPRESS_* variables into Cypress.env()
CYPRESS_ZYNTRA_TEAM_ID=f72c2547
CYPRESS_ZYNTRA_API_KEY=your-api-key

You need two values from the dashboard: an API key (API Keys page) and your team ID (bottom-left of the sidebar). Keep both in environment variables. Getting started shows where they are.

What happens to the email

  1. Your app sends it through its usual email provider, to an address on zyntramail.com.
  2. A real mail server receives it and stores it for your team, with its links pulled out of the HTML and text and its attachments kept.
  3. Your test reads it with one API request. Mail to an address you did not create in the dashboard is deleted after 1 day.

Keep the API key out of the repo

Locally, put ZYNTRA_API_KEY and ZYNTRA_TEAM_ID in a git-ignored cypress.env.json. In CI, set CYPRESS_ZYNTRA_API_KEY and CYPRESS_ZYNTRA_TEAM_ID as secrets: Cypress reads every CYPRESS_ variable into Cypress.env().

When the same address gets a second email

/emails/last returns the newest email at once when the address already has mail. For a second email to the same address, check the subject in .then() and request again until it matches. The password-reset guide shows the pattern.

Limits to know about

  • Cypress's default responseTimeout is 30 seconds, and /emails/last can wait up to 30 seconds. Pass a longer timeout to cy.request, as the example does.
  • If the link opens a different domain from your baseUrl, run the steps on that page inside cy.origin().
  • Every API call counts toward your plan's API requests: one read of an email is one request.
  • Mail to these addresses is kept 1 day. Create a permanent inbox in the dashboard to keep it longer.

Questions

Do I need a Cypress plugin or a task?
No. cy.request calls the API directly from the spec.
Why not a local mail catcher?
A local catcher only sees mail your app sends to a local SMTP server. With Zyntra the email goes through your real provider on staging, so the test covers delivery as well as content.
How do I find the link I need?
Every email comes with links: an array of { url, text } found in its HTML and text. Pick the one whose URL or text matches.

Add the email step to your Cypress suite

Free plan, no setup: send to any address and read it back.

Start free