Testing & CI

Email tests that wait, not sleep

Your test waits for the email, gets its one-time code and sign-in link, and carries on. Wait for a person's reply the same way, skip days ahead when a test needs time to pass, and watch every run in the dashboard.

simmail · activity
The Activity page of a help-desk test universe: a timeline of replies and plans, and an Up next queue
Wait for an email

Codes and magic links, already pulled out

One call holds open until the email arrives, for up to five minutes, then returns it with every link, any one-time code, and the verify, reset or sign-in links. No polling, no sleeps.

Wait for a person's reply in a thread the same way. If they decide not to answer, the wait says so at once instead of timing out.

The wait API in the docs

simmail · activity · sign-up tests
The Activity timeline of a sign-up test run: verification emails delivered and scored, links opened, one address bounced
A sign-up run: verification emails delivered, their links opened, and an old address bounced.
The clock

Live through a week in seconds

Each universe has its own clock. Run it live, faster or frozen, or skip ahead. Everything that falls due on the way runs in order: replies, kept promises, reminders and inbound mail.

Most tests never touch it. Move it when you test what happens over time: a promise that slips, a follow-up two days later.

The simulated clock in the docs

simmail · dashboard
The dashboard's top bar: simulated date and time, the clock speed set to live, and a Skip ahead menu
Simulated time, its speed, and Skip ahead, in the top bar of every page.
CI

Nothing to run inside the job

Your app sends to simmail over SMTP; your tests wait over HTTPS. Give CI its own universe and key, and store them as secrets.

  • SDKs for JavaScript and Python @plexuslabs/simmail and simmail.
  • Quiet throwaway addresses Turn strangers off for your test domain so sign-up tests do not create people.
  • Events as they happen Server-Sent Events or signed webhooks.

CI with GitHub Actions · SDKs

simmail · mail
A receipt email sent by the app during a test, open in the dashboard on its Health tab
Every email your tests trigger is in the dashboard too, with its health report.
The code

A sign-up test in a dozen lines

</> For developers: tests, waits and the clock
import { test, expect } from "@playwright/test";
import { Simmail } from "@plexuslabs/simmail";

const mail = new Simmail(); // reads SIMMAIL_URL and SIMMAIL_API_KEY

test("new users can verify their email", async ({ page }) => {
  const email = `user-${Date.now()}@yourcompany.test`;
  await page.goto("/signup");
  await page.fill("#email", email);

  const waiting = mail.waitForEmail({ to: email, subject: "verify", timeout: 20000 });
  await page.click("text=Create account");
  const msg = await waiting;

  expect(msg.codes).toHaveLength(1);
  await page.fill("#code", msg.codes[0]); // or: await page.goto(msg.magic_links[0])
});

Read the docs →

Questions

Do my tests have to poll?

No. GET /api/wait holds the request open until a matching email arrives, for up to five minutes, then returns it. Give your HTTP client a timeout a little longer than the wait’s.

The email was sent before my wait started. Is it lost?

No. By default a wait only sees mail that arrives after it starts, so start it before the click that sends. Or pass existing=true (existing: true in the SDKs) and narrow it with subject or contains.

What is the difference between 408 and 409 on a reply wait?

408: nothing arrived before the timeout. 409, with fast=true: the person sat down to write and decided not to reply, and nothing else is planned, so waiting longer would not help. People do that; set timing.reply_rate to 1 when a test needs an answer.

Can parallel CI runs share a universe?

Yes. Put the run id in every address your tests create so one run never catches another’s email. Keep CI in its own universe so its clock jumps and test people never touch Staging.

Our sign-up tests create thousands of addresses. Will they all become people?

Only if you let them. Turn strangers off for your test domain in the recipient rules: mail still lands in a mailbox, so waits work, but nobody answers and no people or AI actions are used.

Put real behaviour between your app and its inbox

Start free with 25 AI actions a month, no card needed. Upgrade when you need more.