DEV Community

Cover image for Testing Email Workflows Without Email Server — With Playwright & Mokapi
Maesi
Maesi

Posted on

Testing Email Workflows Without Email Server — With Playwright & Mokapi

Email is one of those things that's genuinely hard to test. It goes out through an SMTP server, lands in a real inbox, and you have no programmatic way to check what arrived. So most teams either test it manually or just assert that sendEmail() was called and call it done.

Neither tells you if the subject line was right, the link was valid, or the HTML rendered correctly.

That's why I built email support into Mokapi. Your backend connects to Mokapi's SMTP server exactly as it would a real mail server. Mokapi captures the message. Your test fetches it over HTTP and asserts on the content. No real emails, no inbox polling, no external dependencies.


The Config

Unlike Kafka or HTTP, email doesn't have a standard specification format. So Mokapi uses a simple YAML config. This is all you need:

mail: '1.0'
info:
  title: "Email Workflows"
servers:
  smtp:
    host: :2525
    protocol: smtp
Enter fullscreen mode Exit fullscreen mode

Point your backend at localhost:2525 and Mokapi starts capturing everything.

Want IMAP too, so you can preview emails in a real mail client during development? Add one line:

  imap:
    host: :1430
    protocol: imap
Enter fullscreen mode Exit fullscreen mode

The Playwright Test

The test drives a real signup form, then fetches the captured email from Mokapi's HTTP API and asserts on it.

test('Email verification after signup', async ({ page, request }) => {
  const recipient = randomEmail();

  // Drive the signup form
  await page.goto('');
  const form = page.getByRole('form', { name: 'Sign Up' });
  await form.getByLabel('Email').fill(recipient);
  await form.getByLabel('Password').fill('SuperSecure123!');
  await form.getByRole('button', { name: 'Sign Up' }).click();
  await expect(form.getByText('Check your inbox to verify your email')).toBeVisible();

  // Fetch the email from Mokapi
  const mails = await request.get(
    `http://localhost:8080/api/services/mail/Email%20Workflows/mailboxes/${recipient}/messages?limit=1`
  );
  const mailList = await mails.json();
  expect(mailList[0]).toEqual(expect.objectContaining({
    subject: 'Verify your email address',
    from: expect.arrayContaining([expect.objectContaining({ address: 'noreply@example.com' })]),
    to: expect.arrayContaining([expect.objectContaining({ address: recipient })])
  }));

  // Check the body and verification link
  const res = await request.get(
    `http://localhost:8080/api/services/mail/messages/${mailList[0].messageId}`
  );
  const mail = await res.json();
  expect(mail.body).toContain(`verify?email=${encodeURIComponent(recipient)}`);
});
Enter fullscreen mode Exit fullscreen mode

Random email per test run keeps things isolated. Playwright's request object handles the Mokapi API calls directly, so the same test that drives the browser also inspects the email. No extra HTTP client needed.


The IMAP Bonus

Automated assertions catch broken content. But they don't catch a layout that looks terrible in Outlook. Because Mokapi supports IMAP, you can connect any real mail client to localhost:1430 during development and see exactly what your users will see. It's the visual check your test suite can't do.


Full Walkthrough

This is the short version. The complete guide on my site covers the full config, the Express backend, all the Playwright helpers, and how it all fits together.

👉 Read the full guide here

Working repo: mokapi-email-workflow

Happy to answer questions in the comments.

Top comments (0)