mails0 vs testmail.app

mails0 vs testmail.app: tagged test inbox or agent identity?

testmail.app gives automated tests flexible namespace addresses and live message queries. mails0 gives each agent or test a scoped mailbox identity with lifecycle, search, threads, extraction, events, and self-hosting.

By Evidence checked Editorial method

Open the quickstart
Direct answer

Choose testmail.app when your test suite benefits from namespace-based wildcard addresses, tags, JSON or GraphQL queries, and a lightweight managed API built around receiving test mail. Choose mails0 when the mailbox itself needs scoped credentials, automatic agent bootstrap, verification-code tools, threads, webhooks, MCP or CLI access, optional sending, and an open-source Cloudflare deployment. For parallel CI, both can isolate runs; testmail.app commonly does it with address tags, while mails0 can use separate mailbox identities and credentials.

Visual guide

Two ways to isolate a parallel email test

Both avoid a shared inbox, but they identify the run differently.

  1. 01
    Allocate run ID

    Create a unique CI or browser-test identifier before triggering mail.

  2. 02
    Map the address

    Use a namespace tag or create a dedicated scoped mailbox.

  3. 03
    Wait after the trigger

    Query only messages that belong to the current run and time window.

  4. 04
    Assert and clean up

    Validate the application state and apply the product retention model.

Best-fit boundary

Choose by the job you need to complete

Choose testmail.app

Address tags are the cleanest CI isolation

Your suite needs disposable recipient variations, live queries, and straightforward JSON or GraphQL retrieval without operating infrastructure.

Choose mails0

Each actor needs a bounded mailbox capability

Credential scope, explicit lifecycle, MCP and CLI operations, threads, events, or self-hosted control are part of the requirement.

Identity model

Namespace tags and mailbox keys solve isolation at different layers

testmail.app documentation describes a namespace with wildcard test addresses. A test can add a unique tag to the address, send a message, and query for that tag. This is attractive in CI because the test runner does not need to create and delete a separate account for every recipient variation.

mails0 can create a separate provisional mailbox with its own credential. The identity is heavier than a tag, but it also becomes a security and lifecycle boundary. An agent can hold one mailbox scope, retrieve only its own data, and later discard or deliberately promote the identity.

Neither model removes the need for a trigger timestamp and an explicit timeout. Tags prevent cross-run collisions only when the application preserves the recipient and the query actually filters on the unique value.

Capability map

Compare the smallest contract your automation needs

Decision pointtestmail.appmails0
IsolationNamespace plus unique address tagDistinct mailbox plus scoped credential
Query interfacesJSON API, GraphQL API, and live queriesHTTP, Python, CLI, MCP, search, webhooks, and SSE
OTP flowQuery messages and parse the code in test logicDedicated wait and extraction operation plus message access
Conversation stateMessage retrieval centered on test addressesMailbox search, labels, threads, replies, and forwarding
RetentionDocumented plan-based message retentionHosted provisional lifecycle or self-hosted retention policy
DeploymentManaged serviceHosted sandbox and MIT-licensed self-hosting
CI design

Make the test deterministic before optimizing the polling call

Create the unique recipient before the browser or API action. Record the trigger time immediately before submission. Query for the recipient, expected sender context, subject or message type, and received time. Stop at a bounded deadline and report whether the application failed to send, the provider delayed delivery, the parser failed, or the final verification rejected the value.

A fixed sleep is simple but slow and flaky. A live query, long poll, webhook, or bounded polling loop should end as soon as the matching message arrives. It should not keep the build alive indefinitely. Parallel jobs must never read from one unfiltered inbox and select the newest message globally.

  1. 01

    Generate a run-specific recipient

    Use a unique testmail.app tag or a new mails0 mailbox.

  2. 02

    Trigger once and mark time

    Avoid repeated requests that generate competing codes.

  3. 03

    Wait with exact filters

    Require the current recipient, sender context, and time range.

  4. 04

    Assert the final product state

    A delivered message alone is not a successful user journey.

Agent integration

The difference grows when the recipient acts outside the test runner

A pure test runner can compose API queries in code. A coding agent or MCP client benefits from higher-level operations with narrow outputs: create an inbox, wait for a code, search messages, fetch one thread, or download one approved attachment. Those operations reduce raw mailbox content in model context.

mails0 also defines a path from temporary receiving to a permanent identity and optional sending. That broader lifecycle is unnecessary for many tests. If tags and message retrieval solve the entire job, testmail.app may be the more focused choice.

  • Prefer testmail.app when namespace addressing already maps cleanly to your test runner.
  • Prefer mails0 when credentials and mailbox lifecycle are part of the agent boundary.
  • Do not grant sending authority just because the receive test passed.
  • Keep product-specific query code behind a small adapter if provider portability matters.
Migration

Map tag semantics before moving to mailbox semantics

A suite may generate thousands of tagged recipients under one namespace without tracking mailbox objects. Moving that suite to per-mailbox provisioning changes setup cost, cleanup, credential storage, and concurrency. Test those effects before converting every case.

The reverse move also changes authorization. A tag can identify a test message without being a credential boundary. If the existing system relies on per-mailbox revocation or separate retention, preserve those requirements in the application layer. Export enough message and run identifiers to compare both systems during a bounded dual run.

Use tags

High-volume ephemeral test recipients

Keep the addressing model simple when identity continuity and independent credentials are unnecessary.

Use mailboxes

Independent actors and durable state

Choose explicit identity when security scope and lifecycle need their own object.

Abstract carefully

Preserve timestamps and result states

A provider adapter should not flatten timeout, ambiguity, and parse failure into one empty response.

Implementation judgment

Benchmark the whole test, not only message lookup latency

Run the same parallel verification suite with realistic concurrency. Measure recipient allocation, trigger-to-match time, false matches, timeout diagnosis, credential handling, cleanup, and final application success. The better fit is the model that keeps the suite deterministic with the least operational and security overhead.

Evidence

Sources and product scope

Product behavior is verified against the mails0 source and documentation. External comparisons link to official vendor documentation checked on 2026-08-15.

Questions and answers

Questions about mails0 vs testmail.app

Does testmail.app support GraphQL?

Yes. Its official documentation describes JSON and GraphQL APIs, live queries, namespace addresses, tags, and examples for automated email testing.

Which is simpler for parallel CI email tests?

testmail.app tags can be very simple for high-volume ephemeral recipients. mails0 mailboxes add independent credentials and lifecycle when each run or actor needs a stronger boundary.

Can mails0 wait for an OTP directly?

Yes. The CLI and MCP tools include bounded verification-code retrieval, while the API also exposes messages for custom parsing and assertions.

Start with a bounded inbox

Prove the smallest useful email loop.

Start with one scoped inbox and one expected message. Add durable identity, sending, and operational complexity only when the first loop works.

Open the quickstart