Back to blog
QA Automation: How to Validate Sign-up Emails with Playwright and JunkMail API

QA Automation: How to Validate Sign-up Emails with Playwright and JunkMail API

E2E tests often stop at email sending. Learn how to close the loop and test clicking the confirmation link using a programmable email API.

By Leandre1/5/2026

It's the classic nightmare of the QA Engineer or Fullstack Developer: testing the Sign Up Flow.

The scenario is simple on paper:

  1. User fills out the form.
  2. User receives an email with a magic link.
  3. User clicks the link.
  4. Account is activated.

With modern tools like Playwright or Cypress, steps 1 and 4 are trivial. But steps 2 and 3 are often a black hole. How to verify that an email was really sent? How to retrieve the validation link inside it to click on it programmatically?

Often, dev teams use hacks:

  • Connecting to a real Gmail box via IMAP (slow, unstable, Google often blocks the connection).
  • Mocking email sending (fast, but doesn't test real deliverability or HTML template).
  • Using Mailhog/Mailcatcher locally (good, but doesn't work in Staging/Prod).

The robust solution? Use a disposable and programmable email API like JunkMail.

The Test Architecture

Here is what we are going to build:

  1. Playwright generates a unique email address via the JunkMail API.
  2. Playwright fills your app's signup form with this email.
  3. Playwright polls the JunkMail API until the message is received.
  4. Playwright extracts the validation URL (Regex) from the message body.
  5. Playwright navigates to this URL and verifies success.

Prerequisites

  • Node.js installed.
  • A Playwright project (npm init playwright@latest).
  • A JunkMail API Key (available in your Business dashboard).

The Code

Here is a complete example in TypeScript.

1. The JunkMail API Client

First, let's create a small helper to interact with JunkMail.

// utils/junkmail.ts
import { request } from '@playwright/test';

const JUNKMAIL_API_URL = 'https://junkmail.site/api/v1';
const API_KEY = process.env.JUNKMAIL_API_KEY;

export async function createTempEmail() {
  const context = await request.newContext();
  const response = await context.post(`${JUNKMAIL_API_URL}/emails`, {
    headers: { 'Authorization': `Bearer ${API_KEY}` }
  });
  return await response.json(); // Returns { id: "...", address: "xyz@junkmail.site" }
}

export async function waitForEmail(addressId: string, timeout = 30000) {
  const context = await request.newContext();
  const startTime = Date.now();

  while (Date.now() - startTime < timeout) {
    const response = await context.get(`${JUNKMAIL_API_URL}/emails/${addressId}/messages`, {
      headers: { 'Authorization': `Bearer ${API_KEY}` }
    });
    const messages = await response.json();
    
    if (messages.length > 0) {
      return messages[0]; // Return the first received message
    }
    
    // Wait 1s before retrying
    await new Promise(r => setTimeout(r, 1000));
  }
  throw new Error('Email not received in time');
}

2. The E2E Test

Now, let's use this helper in our Playwright spec.

// tests/signup.spec.ts
import { test, expect } from '@playwright/test';
import { createTempEmail, waitForEmail } from '../utils/junkmail';

test('A new user can sign up and validate their email', async ({ page }) => {
  // 1. Create a unique email address for this test
  const tempEmail = await createTempEmail();
  console.log(`Generated test email: ${tempEmail.address}`);

  // 2. Fill the signup form
  await page.goto('https://my-app.com/register');
  await page.fill('input[name="email"]', tempEmail.address);
  await page.fill('input[name="password"]', 'Password123!');
  await page.click('button[type="submit"]');

  await expect(page.locator('text=Check your emails')).toBeVisible();

  // 3. Wait for email reception (JunkMail side)
  const message = await waitForEmail(tempEmail.id);
  console.log(`Email received: ${message.subject}`);

  // 4. Extract validation link
  // Looking for an URL like https://my-app.com/verify?token=...
  const linkRegex = /https:\/\/my-app\.com\/verify\?token=[a-zA-Z0-9-]+/;
  const match = message.body.text.match(linkRegex);
  
  if (!match) throw new Error('Validation link not found in email');
  const verificationUrl = match[0];

  // 5. Visit validation link
  await page.goto(verificationUrl);

  // 6. Verify account is activated
  await expect(page.locator('text=Account activated successfully')).toBeVisible();
});

Why is it better than other methods?

Data Isolation

Each test uses a fresh email address (test-123@junkmail.site, test-456@junkmail.site). You risk no data conflict (e.g., "This email already exists") if you run your tests in parallel.

End-to-End Testing

Unlike mocks, you test:

  • That your sending service (SendGrid, AWS SES...) works.
  • That your HTML template doesn't break the link.
  • That the validation link actually works on the browser.

Stability and Speed

The JunkMail API is designed for developers. It is fast, RESTful, and does not require complex IMAP configuration or 2FA bypassing like with Gmail.

The Philosophy Moment: QA vs Reality

A test that doesn't reproduce reality isn't a test, it's a hope. Your users don't "mock" their emails. They really expect to receive them. If your email sending system goes down (quota exceeded, IP blacklist), your mocked tests will still be green, but your business will be at a standstill.

Testing with real email reception infrastructure is the only way to have absolute confidence in your Sign Up "Happy Path".

Conclusion

Automating email tests shouldn't be a pain. With the right tools, it's as simple as an API call. Integrate JunkMail into your CI/CD pipeline and stop crossing your fingers at every deployment.

Ready to code? Get your API key on JunkMail Developer.