Skip to content
Developer tooling Private beta Laravel · Go

MailHulk: real inboxes for automated tests

Signup, password reset, magic link, one-time code: the flows that matter most are the ones a test suite handles worst, because an email has to arrive. MailHulk gives each test its own inbox, captures the message, and hands back the code and links already parsed — so the test asserts on a real email instead of a mock, and never calls sleep().

MailHulk inbox: a captured verification email opened in the message viewer, with the six-digit code and link count extracted underneath
4Ways to read a message: REST, webhook, POP3, IMAP
3SDKs (JS, PHP, Python), plus a CLI, MCP server and GitHub Action
?wait=One bounded long-poll replaces every retry loop
BetaPrivate, receive-only; launch not yet dated

The problem

Teams running Playwright, Cypress or API tests in CI all hit the same wall. The email either goes to a shared inbox that parallel runs fight over, or it is mocked away and the flow is never really tested. When a run fails, the evidence is a bare “no message” and someone re-runs it by hand. The workaround is usually a polling loop with a sleep in it, which is slow when it works and silent when it does not.

What it does

A test creates an inbox, triggers the app under test, then waits on one call that returns as soon as the message lands or the deadline passes. The response carries codes[] and links[] already extracted, so the next line of the test is the assertion. Ephemeral inboxes expire on their own; persistent mailboxes on a custom domain serve nightly regression runs. The same message is available by signed webhook, and over POP3 and IMAP for tools that only speak mail.

Around that core sit the pieces a QA team ends up building anyway: a scenario engine that runs deterministic email journeys and keeps the evidence, an authenticator vault so a TOTP code lands next to the email instead of on someone’s phone, an Android companion that puts the code into the form on the device being tested, and an MCP server so an AI agent can drive the same API.

Built with restraint

Mail ingestion is a separate Go service that knows nothing about tenants; it writes to object storage and a queue, and the Laravel side does the rest. That boundary is what keeps the receiving path simple enough to trust. MailHulk does no AI inference of its own, and the roadmap deliberately avoids “unlimited” as a headline: quotas are the product being honest about cost.

Where it is today

The application and the real mail plane are built and running on our own infrastructure. Outbound sending is switched off on purpose while the product is receive-only. Payments through a merchant of record are in progress; private beta and public launch are planned and not yet dated. There are no customers to quote, so this page quotes none.

At a glance
ForProduct teams running end-to-end tests in CI
ShapeHosted, API-first, team & project tenancy
StackLaravel 12, Go mail service, RabbitMQ, S3, Dovecot
PlansFree, Dev, Team, Business; 14-day trial
StatusPrivate beta, receive-only
Capabilities
Ephemeral inboxes with a TTL, persistent mailboxes on your domain Long-poll wait, latest-message and parsed codes and links Signed webhooks with retries and a delivery log Custom domains with MX and DKIM verification POP3 and IMAP served by real mailboxes Scenario engine with runs, approvals and evidence TOTP authenticator vault beside the inbox Scoped API keys, audit log, 2FA and passkeys
Evidence status

Screens on this page are MailHulk running on a seeded demonstration database: every team, inbox, sender and message is fictional. No usage figures or customer names are shown because the product has not launched.

The product

Four screens, one loop

Create an inbox, trigger the app, wait for the message, assert on what was parsed. Everything else on the dashboard exists to make that loop observable when it breaks.

Inbox list for a project: address, latest message, status and expiry, storage used and message count per inbox
Inboxes. Persistent mailboxes and ephemeral test inboxes side by side, each with its expiry, storage and message count. Team-level quota sits at the top so nobody discovers a limit from a failed run.
QA runs table listing scenario runs with passed, running and failed status, the agent and trigger, and duration
QA runs. Published scenarios run deterministically, from the dashboard or the API, and every run keeps its step timeline and evidence for the retention window.
Webhook endpoints with health, delivery counts and an enabled toggle; one endpoint flagged as failing
Webhooks. A signed message.received event per endpoint, with health, recent deliveries and failures called out. A failing sink is visible before it costs a build.
Project dashboard with inbox, message, webhook and API key counts, a needs-attention panel, recent messages and the team plan
Project dashboard. One project at a time: what is healthy, what needs attention, the newest mail, and the plan limits in plain numbers.
Decisions

Three calls that shaped it

Parse on the server, not in every test Codes and links are extracted once at ingestion. A test suite in any language reads the same fields instead of shipping its own regexes.
Keep ingestion stateless and tenant-blind The mail plane accepts, stores and enqueues. Tenancy, quotas and fan-out happen in the application, where they can be tested and changed.
Speak the protocols people already use A REST API is not enough for every tool. Real POP3 and IMAP mailboxes mean a legacy runner or a mail client can read the same inbox.
Testing email flows in CI, or need a system built on these foundations? Ask about beta access, or start with a Blueprint for the product you actually need.
Request a call →