If you write Playwright or Cypress tests, you already know the exact moment the joy leaves your body.
Testing the UI is easy. But the second your test runner hits a "Check your email for the login code" screen, everything falls apart.
I know the email testing space is already heavily saturated, but if you're just trying to verify an auth flow, developers are still forced into two terrible options:
- The duct-tape method: Wiring up a shared Gmail account via IMAP, writing brittle polling scripts, and praying that concurrent tests don't steal each other's OTPs.
- The overkill method: Adopting massive, expensive enterprise email QA platforms that feel like bringing a bazooka to kill a fly.
There is a gap for a lightweight tool that just does one thing: provisions an isolated inbox on the fly, hands you the OTP or magic link, and gets out of your way.
So, I built PostMX to fill that gap.
I am launching the V1 today, and to be completely transparent, I have exactly zero users. But I want to share the architecture of how it works, because even if you never touch my API, moving to ephemeral inboxes is the only way to stop your CI pipeline from being flaky.
Instead of a static inbox, you treat the email address as a temporary API object. You spin up a fresh inbox for a specific test run, trigger your app, and grab the extracted data as clean JSON. No Regex, no IMAP rate limits.
Here is what the execution looks like using the Node SDK I just shipped:
import { test, expect } from '@playwright/test';
import { PostMX } from "postmx";
const postmx = new PostMX(process.env.POSTMX_API_KEY);
test('User can sign in via magic link', async ({ page }) => {
// 1. Spin up a fresh, isolated inbox just for this test run
const inbox = await postmx.createTemporaryInbox({ label: "signup-test" });
await page.goto('https://your-app.com/login');
// 2. Use the ephemeral email address in your UI
await page.fill('input[name="email"]', inbox.email_address);
await page.click('button[type="submit"]');
// 3. Wait for the email and grab the first extracted link
const email = await postmx.waitForMessage(inbox.id);
const magicLink = email.links[0];
// 4. Complete the auth flow
await page.goto(magicLink);
await expect(page.locator('.dashboard')).toBeVisible();
});
I wanted this to be the lowest-friction way possible to get tests passing reliably.
The API and the docs are live at postmx.co.
Since I'm starting from absolute zero today, I would genuinely appreciate any harsh feedback. Please roast the implementation, the docs, or tell me why this architecture wouldn't work for your specific CI pipeline. I'll be in the comments answering everything.
Top comments (1)
Readers, let me know if you have any questions!