Emailens
Email Sandbox

Automated Testing in CI/CD

Run end-to-end authentication and transaction tests with Playwright, Cypress, or Vitest using the Emailens wait API.

Testing transactional emails, two-factor authentication (2FA), and magic links in CI/CD pipelines often suffers from timing flakiness. The Emailens Sandbox provides a long-polling wait API that blocks until a matching email arrives, returning extracted tokens and URLs ready for automation.


Polling Endpoint: /api/sandbox/v1/messages/wait

Instead of polling repeatedly in a tight loop with sleep(), your test runner sends a single request that stays open until a matching email is ingested.

Request Syntax

GET https://app.emailens.dev/api/sandbox/v1/messages/wait?inboxId={INBOX_ID}&timeout=30&subject=Verify HTTP/1.1
Authorization: Bearer ek_live_...

Query Parameters

ParameterTypeRequiredDescription
inboxIdstringYesTarget sandbox inbox ID
timeoutnumberNoMaximum wait duration in seconds (default 30, max 60)
subjectstringNoOptional case-insensitive substring filter on email subject
tostringNoOptional recipient filter

Response Payload

When the message arrives, Emailens returns HTTP 200 with parsed fields:

{
  "message": {
    "id": "msg_98127391",
    "inboxId": "inbox_clx9abc",
    "subject": "Your verification code: 492019",
    "from": "auth@myproduct.com",
    "to": "test-runner@try.emailens.dev",
    "otp": "492019",
    "actionUrl": "https://myproduct.com/verify?token=eyJh...",
    "receivedAt": "2026-10-08T12:04:12Z"
  }
}

Playwright End-to-End Example

Here is a complete Playwright test verifying a user signup flow with a 6-digit OTP code:

import { test, expect } from "@playwright/test";

test("user sign-up with email OTP verification", async ({ page }) => {
  const testInboxId = process.env.EMAILENS_INBOX_ID!;
  const apiKey = process.env.EMAILENS_API_KEY!;
  const testEmail = "test-user@try.emailens.dev";

  // 1. Fill sign-up form
  await page.goto("https://myapp.com/signup");
  await page.fill('input[name="email"]', testEmail);
  await page.click('button[type="submit"]');

  // 2. Wait for confirmation input screen
  await expect(page.locator('input[name="otp"]')).toBeVisible();

  // 3. Wait for the email in the Emailens sandbox
  const waitResponse = await fetch(
    `https://app.emailens.dev/api/sandbox/v1/messages/wait?inboxId=${testInboxId}&timeout=30&subject=verification`,
    {
      headers: {
        Authorization: `Bearer ${apiKey}`,
      },
    }
  );

  expect(waitResponse.ok).toBeTruthy();
  const { message } = await waitResponse.json();
  expect(message.otp).toBeDefined();

  // 4. Fill OTP into page and verify dashboard redirects
  await page.fill('input[name="otp"]', message.otp);
  await page.click('button[type="submit"]');

  await expect(page).toHaveURL("https://myapp.com/dashboard");
});

Testing passwordless magic links in API integration tests:

import { test, expect } from "vitest";

test("request magic link and verify token session", async () => {
  const inboxId = process.env.EMAILENS_INBOX_ID!;
  const apiKey = process.env.EMAILENS_API_KEY!;

  // Trigger magic link dispatch
  await fetch("https://myapp.com/api/auth/magic-link", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ email: "tester@try.emailens.dev" }),
  });

  // Long poll Emailens
  const response = await fetch(
    `https://app.emailens.dev/api/sandbox/v1/messages/wait?inboxId=${inboxId}&timeout=20`,
    {
      headers: { Authorization: `Bearer ${apiKey}` },
    }
  );

  const { message } = await response.json();
  expect(message.actionUrl).toContain("https://myapp.com/auth/confirm?token=");

  // Follow the magic link
  const authResponse = await fetch(message.actionUrl, { redirect: "manual" });
  expect(authResponse.status).toBe(302);
});

On this page