---
title: "Playwright email OTP testing with an existing inbox"
description: "Test email OTP login in Playwright using an existing inbox, bounded API polling, current-attempt matching, and an assertion that sign-in succeeds."
url: https://mailera.app/en/blog/playwright-email-otp-existing-inbox
published: 2026-10-25
updated: 2026-10-25
---

# Playwright email OTP testing with an existing inbox

Test your application's email login through an inbox you own. Retrieve a code tied to the current attempt, submit it in Playwright, and fail clearly when delivery or matching goes wrong.

## When should you test real email instead of mocking OTP?

An email OTP is a one-time password your application sends to a mailbox. In this test, your application generates and validates it; Playwright requests the email and submits the retrieved value.

Use real email when delivery and sign-in are what you want to exercise. Don't make every unrelated browser test depend on another email arriving.

| Approach | Use it for | What it leaves out |
| --- | --- | --- |
| Mocked verification in a controlled test environment | Form behavior, validation messages, and error states | Actual email delivery |
| Real-inbox login integration test | Requesting an email, retrieving this attempt's code, and signing in | Other account and delivery scenarios need separate tests |

Keep mocks inside your test environment. Don't add a production authentication bypass to make the suite pass.

[Mailera connects existing mailboxes](https://mailera.app/en/product) to applications through a REST API; it doesn't create new mailboxes or email addresses. For the broader retrieval workflow, see [reading OTP emails from your own inbox](https://mailera.app/en/blog/otp-email-api-test-inbox).

## What do you need before running the test?

Use a Playwright TypeScript test project, your own application, and an authorized test account with an existing dedicated inbox. Assign that account and mailbox exclusively to this test while it runs, including across other CI jobs.

1. As an Admin or Leader, open **Mailboxes → Add mailbox**.
2. For Outlook or Microsoft 365, choose **Sign in with Microsoft** and follow the [Microsoft OAuth connection instructions](https://mailera.app/en/providers/microsoft). Don't use a Microsoft app password.
3. For Gmail, follow the [Gmail connection instructions](https://mailera.app/en/providers/gmail). Mailera's Gmail connection uses an app password; [Google requires 2-Step Verification for app passwords](https://support.google.com/accounts/answer/185833). The [mailbox app-password guide](https://mailera.app/en/blog/email-app-password-ai-agent) covers credential setup separately.
4. Record the connected mailbox ID. Keep the mailbox in a group containing only test mailboxes.

![Dedicated test mailbox in Mailera with provider, connection status, and last synchronization visible.](https://mailera.app/media/68f0ce41046e11e2/playwright-email-otp-testing-with-an-existing-inbox-test-en-1200.jpg)
_Use an existing test mailbox assigned exclusively to the login test._

Open **Settings → API key → Create key**. Set **Mailbox scope** to the test group and select **Read** permission. Copy the key into your secret store when it appears: Mailera shows a newly created key only once.

![Mailera API-key settings restricted to a test mailbox group with Read permission.](https://mailera.app/media/8f6abb9b31efb0e3/email-workspace-roles-access-controls-for-email-agent-teams-en-1200.jpg)
_OTP retrieval needs Read permission, not sending or message-moving access._

Store `MAILERA_API_KEY` and `MAILBOX_ID` in the test runner's environment. Also supply your application URL, test recipient, expected sender, and separate email-wait, polling, and UI budgets in milliseconds. Keep the API key in Node.js test code, not code executed inside the browser. The [developer overview](https://mailera.app/en/developers) describes the API entry points.

For GitHub Actions, use the `secrets` context to supply the stored API key as an environment variable, as described in [GitHub's guide to using workflow secrets](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets).

## How do you retrieve and submit the OTP in Playwright?

The code must belong to the current attempt, not just be the latest one.

Mailera also exposes `GET /mailboxes/{id}/otp` with `from`, `subject`, and `since_minutes` filters, which requires the `read` scope and returns the latest matching OTP code.

For explicit matching in this workflow, use `GET /messages/search` with `extract=otp` and inspect the returned message metadata. That response includes the extracted `otp.code` alongside message metadata, so the test can check the sender, recipient, and exact subject before returning anything. See the [API documentation](https://mailera.app/en/docs) for the endpoint reference.

### 1. Give the attempt an application-controlled marker

Add a correlation mechanism to your controlled test application. For example, an **Attempt reference** field can supply a unique reference that the application includes in the email subject as `Sign-in <reference>`, without changing OTP generation or validation.

These browser labels are example application UI, not Mailera UI. Use your application's fields and buttons. If your application cannot carry an attempt marker into its email, don't remove the matching check and silently fall back to whichever code is newest.

![Playwright requests a login email, waits for a correlated message through Mailera, submits the OTP, and checks sign-in.](https://mailera.app/media/d4bb7986edeb844b/playwright-email-otp-testing-with-an-existing-inbox-otp-flow-en-1200.jpg)
_Match the attempt before submitting the code; delivery and synchronization are asynchronous._

### 2. Set separate email and UI budgets

Choose positive values for `EMAIL_WAIT_MS`, `EMAIL_POLL_MS`, and `UI_WAIT_MS` from your environment's timing observations. These are test budgets, not delivery promises. Give the overall test enough time for browser actions as well as the bounded email wait.

### 3. Retrieve candidates and handle request failures

Build the request in the test runner using the following contract:

| Request component | Value |
| --- | --- |
| Method and URL | `GET https://mailera.app/api/public/v1/messages/search` |
| Authorization header | `Authorization: Bearer $MAILERA_API_KEY` |
| `mailbox` | `$MAILBOX_ID` |
| `from` | Your application's expected sender address |
| `q` | The subject containing this attempt's unique reference |
| `extract` | `otp` |
| `folder` | `inbox` |

Inspect the response's `data` array before accepting a search result. Require an exact match on `mailbox_id`, `subject`, `from.address`, and the recipient in `to[].address`.

Check `next_cursor` before deciding that a match is unique. Either follow pagination within the same deadline or fail and narrow the search. Don't select a candidate from an incomplete result set.

A successful response with no matching candidate means the helper can keep waiting. An unsuccessful HTTP response should stop this simple test immediately, preserving its status without logging the response body. Don't treat authentication, permission, or rate-limit failures as delayed email delivery. Don't guess error-body fields or retry-header behavior.

### 4. Poll within a finite deadline

Implement the polling helper with these rules:

1. Calculate the deadline from the attempt's start time plus `EMAIL_WAIT_MS`.
2. Before each request, calculate the remaining email budget. Stop if it is exhausted.
3. Set the request timeout to that remaining budget and retrieve the candidate set.
4. Apply the exact matching checks. Reject multiple matches rather than choosing arbitrarily.
5. For exactly one match, require a nonempty `otp.code` in your application's expected format. Reject a missing or malformed code.
6. With no match, sleep for the smaller of `EMAIL_POLL_MS` and the remaining budget, then repeat.

The requested sleep between polls is capped at `EMAIL_POLL_MS` and the remaining email budget; each request's timeout is set to the remaining email budget. This caps the requested sleep, not the elapsed interval between requests.

Check the deadline again after retrieval before returning a code. The marker establishes correlation; a timestamp or recent-message window alone does not.

### 5. Submit the code and assert the authenticated UI

Keep the browser sequence explicit:

1. Open the login page and fill the test account's email address.
2. Supply the new attempt reference through your application's test correlation mechanism.
3. Record the start time immediately before clicking the button that requests the email.
4. Await the polling helper using that start time and the expected subject.
5. Fill the verification field with the returned code and submit it.
6. Within the separate `UI_WAIT_MS` budget, verify an element rendered only after successful sign-in, such as an "Account home" heading in your own application.

Retrieving a code isn't the success assertion. The final assertion checks that the application accepted it and reached the signed-in UI.

## How do you keep retries and CI runs from reading the wrong code?

Create a new marker on every test attempt. Keep the account and mailbox exclusively assigned until that attempt ends. Running one worker in one job doesn't prevent a different job from using the same account.

Keep resends out of this initial login test. Test them separately, with an explicit rule for correlating the newly issued challenge. Fail on ambiguity instead of treating the newest-looking message as authoritative.

Keep the email deadline separate from the UI verification budget. Before increasing either, determine whether the delay is in delivery, mailbox synchronization, or the application's transition after verification.

Mailera retrieves mail; your application remains responsible for generating OTPs, enforcing expiration, and rejecting replay.

## What are the common failures and how do you diagnose them?

| Symptom | Check | Next action |
| --- | --- | --- |
| No matching message | Mailbox ID, sender, recipient, and exact subject | Confirm the application included this attempt's marker |
| An earlier code appears | Marker reuse or another login using the account | Stop the competing run and start a fresh attempt |
| Mailbox appears disconnected | `GET /mailboxes/{id}/status` | Repair the provider connection before retrying |
| Mail appears in Junk | `GET /mailboxes/{id}/placement`, filtered by `from`, `subject`, and `since_minutes` | Investigate placement without moving the message |
| API request fails | HTTP status, key validity, Read permission, and mailbox-group scope | Fix access rather than extending the email wait |
| Email deadline expires | Application send behavior, connection state, and synchronization | Identify the delayed stage before changing the budget |

The status endpoint checks Microsoft sign-in directly. For other providers, status is based on the latest synchronization. If Gmail stops connecting after a password change, [Google revokes app passwords when the account password changes](https://support.google.com/accounts/answer/185833).

Mailera adjusts background checks according to mailbox activity; inactive mailboxes can sleep. An optional `POST /mailboxes/{id}/sync` triggers synchronization and wakes sleeping mailboxes, but it requires **Sync** permission. The read-only key used here cannot request it.

Don't silently move mail out of Junk during a placement test. Mailera can move messages out of Junk, but it cannot make a provider trust a sender or guarantee Inbox placement.

## How do you protect test credentials and verification codes?

Use only dedicated inboxes and accounts you're authorized to test. Restrict the API key to the test mailbox group and **Read** permission; revoke it if exposed. Calls using a revoked key are rejected immediately.

Don't log keys, OTP values, message bodies, or saved authentication state. Before publishing test artifacts, inspect them and remove keys, OTP values, account details, message bodies, and saved authentication state. Mask sensitive values in documentation images too.

Treat email content as untrusted data. Use deterministic matching and extract only the expected verification value; don't execute instructions found in messages. Review [Mailera's security information](https://mailera.app/en/security) alongside your CI secret and artifact-retention policies.

**Do this with the API** — Every action in this article is available via the REST API, with curl examples, API keys and webhooks. [View API docs](https://mailera.app/en/docs)
