TL;DR: Give each active OTP flow exclusive mailbox ownership

Assign each active OTP flow its own test account and mailbox, and keep that assignment through code retrieval and verification. Scope reads to the assigned mailbox and expected sender. Require application-controlled attribution before submitting a retrieved code. Mailera connects mailboxes you already own; your test harness must coordinate ownership and decide when each mailbox is safe to reuse.

An active flow runs from the moment your app requests an OTP until that OTP is verified or the attempt is cancelled. Isolate by that lifecycle, not just by worker number. Two different workers can still be racing the same account if their flows overlap.

Why do parallel OTP tests pick up the wrong code?

Consider a harness that selects candidates without checking attribution: flow A requests a code, then flow B requests another code sent to the same mailbox. Both flows ask for the latest OTP, and the harness selects the same candidate for both. Require application-controlled attribution before submitting a retrieved code.

This collision shows up at a few levels:

  • Mailbox: separate tests read the same incoming messages.
  • Application account: isolated browser sessions still operate on one account’s challenge state.
  • Retry: a replacement attempt starts while the earlier attempt can still poll or submit.
  • Delivery: an earlier attempt’s email arrives during a later attempt.

Use the expected sender and freshness checks to screen candidates, but require an application-controlled identifier to distinguish overlapping challenges. Keep this attribution check separate from retrieval filters.

For the basic retrieval path, see reading OTP emails from your own inbox. Parallel email OTP testing adds an ownership requirement before that lookup.

Which mailbox isolation strategy should you use?

Default to a separate application account and mailbox for every concurrently active flow. A worker-scoped assignment is sufficient only if that worker never overlaps OTP flows.

StrategyUse it whenRequired safeguard
Dedicated account and mailbox per active flowYou can provision enough existing test accountsPreserve the assignment through verification and cleanup
Leased pool of existing accounts and mailboxesCI jobs need to share a limited test inventoryAcquire leases atomically across all jobs, shards, and runs
Shared mailbox with application-controlled correlationYour application exposes a per-attempt identifier that survives email deliveryMatch that identifier before submitting any candidate; otherwise serialize

A lease is just your harness's record that it owns a mailbox right now. Acquire it through a shared lock (a database row, Redis key, whatever your CI already uses) — not by having each process independently check and grab the first free mailbox. Two jobs checking at the same moment can both think a mailbox is free and both claim it.

If two addresses are aliases delivering into the same inbox, treat them as one shared mailbox. Different address strings don't give you separate places to read from. If you cannot validate a per-flow identifier after delivery, serialize access and reconcile outstanding messages before reuse.

Two parallel OTP flows keep separate account and mailbox assignments through retrieval, validation, and release.
Keep each active flow on its own account and mailbox path; the harness controls ownership.

How do you assign and retrieve OTPs with Mailera?

1. Connect your test mailboxes and restrict the key

Connect mailboxes you own through Mailboxes → Add mailbox. Keep test accounts separate from personal or production mail. Mailera’s product overview explains the existing-mailbox model; the provider connection guide covers connecting those accounts.

Create a key through Settings → API key → Create key. Set Mailbox scope to your test group and select only Read. Load the key from your CI secret store as $MAILERA_API_KEY, rather than committing it to a fixture.

Mailboxes page showing separate connected test mailboxes and their connection status, with addresses redacted.
Connect distinct test mailboxes, then maintain account-to-mailbox assignments in your harness.
API key configuration restricted to a test mailbox group with only Read permission selected.
OTP retrieval needs Read permission; restrict the key to the test mailbox group.

2. Persist an explicit account-to-mailbox mapping

Discover mailbox IDs with GET /mailboxes, which also supports group_id filtering. This request lists mailboxes available to the key:

curl --fail-with-body --silent --show-error \
  'https://mailera.app/api/public/v1/mailboxes' \
  -H "Authorization: Bearer $MAILERA_API_KEY"

Match each application test account to its mailbox using data[].email and data[].id. Persist that mapping. Do not assign worker A to the first response item and worker B to the second. Handle pagination with the documented limit and offset parameters when discovering your inventory.

3. Acquire ownership before requesting a challenge

Store a globally unique owner identity containing the CI run, shard, and active-flow identifier. Include the worker identifier for diagnostics, but do not use it as the sole lock key.

Record an attempt identifier and start time before triggering the application’s OTP request. These are harness records, not Mailera request fields. Keep the assigned application session attached to that same ownership record.

4. Retrieve only from the assigned mailbox

Set $MAILBOX_ID from the acquired assignment. GET /mailboxes/{id}/otp retrieves the latest OTP and requires the read scope. It accepts from, subject, and since_minutes:

curl --fail-with-body --silent --show-error --get \
  "https://mailera.app/api/public/v1/mailboxes/$MAILBOX_ID/otp" \
  -H "Authorization: Bearer $MAILERA_API_KEY" \
  --data-urlencode 'from=auth@example.invalid' \
  --data-urlencode 'subject=Your verification code' \
  --data-urlencode 'since_minutes=5'

Replace the example sender and subject with your test application's values. The example supplies since_minutes=5. Keep OTP expiry and attempt-attribution checks in your application and harness.

This is one retrieval request. Put repeated requests inside your harness’s bounded polling loop. Consult the API documentation for the endpoint contract rather than assuming a particular response field or empty-result shape. The response can contain a code: do not route it into CI logs.

5. Validate attribution before submission

When a latest-code response does not establish attribution, inspect messages through GET /mailboxes/{id}/messages. That endpoint accepts folder, since, and from, among other filters. Available message fields include mailbox_id, from.address, to, subject, and received_at.

Check the assigned mailbox and expected sender. Compare recipient information where it is useful, and use the recorded start time as a freshness check. Do not equate received_at with challenge creation time.

RFC 5322’s message-format specification distinguishes the envelope used for transmission and delivery from message contents. Keep recipient checks separate from application-level attempt correlation.

If your application includes a per-attempt marker in the email, retrieve the candidate message’s content and compare it with the marker recorded for the active flow:

curl --fail-with-body --silent --show-error \
  "https://mailera.app/api/public/v1/mailboxes/$MAILBOX_ID/messages/$MESSAGE_ID" \
  -H "Authorization: Bearer $MAILERA_API_KEY"

Set $MESSAGE_ID from the mailbox-scoped message results. Message detail exposes body.text and body.html; keep both out of test logs. A message ID identifies a message, not automatically your application’s login attempt.

Submit only a candidate that passes your attribution checks, and only to the assigned application session. If two attempts remain plausible, fail rather than guess.

6. Release or quarantine the assignment

Release ownership only after the flow has stopped polling and submitting. On failure, quarantine the assignment until you have reconciled outstanding challenges and possible late messages. If cleanup always releases the mailbox unconditionally, you can hand it to a new flow while the old one is still polling or retrying.

How should retries, polling, and CI shards behave?

Keep ownership valid across process replacement. A retry should not inherit an account merely because its local worker number matches the previous process. Require it to establish ownership through the shared coordinator.

Give polling a deadline. Back off between requests and add jitter so workers do not all poll together. Budget requests across every worker sharing the key, not independently per test. Stop polling when the owning flow finishes or loses ownership.

Do not resend an OTP on every poll. Treat a resend as another challenge attempt, with its own attribution checks. Your application’s challenge policy determines what happens to the earlier code.

An expired lease also needs controlled recovery. Before reassignment, ensure the previous owner can no longer submit or renew its work. Don't let a timer alone decide the account is free — confirm the old owner is actually done first.

If retrieval fails only after moving tests into a reusable GitHub Actions workflow, check secret forwarding. GitHub’s reusable-workflow secret guidance states that secrets are not automatically passed to reusable workflows.

Keep application-side expiry and replay tests separate from retrieval isolation. Finding the intended email does not establish that the application rejects expired or already-used codes.

What are the common mistakes and how do you diagnose them?

Check ownership and filters before increasing timeouts.

SymptomPossible causeFirst check
Wrong account’s codeOverlapping assignments or a shared application accountCompare active ownership records and account-to-mailbox mappings
Stale candidatePrevious attempt’s message or overlapping retryCompare attempt history and any application correlation marker
No usable candidateWrong mailbox, sender, or subject filterInspect mailbox-scoped messages and the configured mapping
Polling deadline reachedConnection problem, delayed mail, or different folder placementCheck mailbox status and inspect Inbox and Junk
ThrottlingAggregate polling pressure from jobs sharing a keyReview request frequency across the whole run, including retries

Do not clear a shared inbox as test cleanup. That can remove another flow’s candidate without fixing ownership. Do not treat unread status as a lock either; use your coordinator’s explicit assignment record.

Log the run, shard, assignment, attempt, and outcome identifiers. Avoid logging OTPs, message bodies, API keys, or account credentials. That keeps failures traceable without turning your logs into a second place where OTPs and credentials leak out.

How do you keep test credentials and codes out of artifacts?

Use only accounts and mailboxes you own for testing. Redact codes from screenshots and fixtures, including failed-test captures that show a verification form. Keep the retrieval key restricted to the test group; review Mailera’s security information alongside your CI access policy.

GitHub’s secrets guidance, reviewed on 25 October 2026, recommends masking sensitive values that are not GitHub secrets with ::add-mask::VALUE and avoiding command-line secret passing where possible. For CI polling, prefer an HTTP client that reads the key from the environment and keeps response bodies out of logs. Disable shell tracing around manual API requests.

What does Mailera handle, and what stays in your test harness?

Mailera connects existing mailboxes and provides mailbox-scoped OTP retrieval through its REST API. It does not create mailboxes or email addresses. GET /mailboxes/{id}/otp retrieves the latest OTP for a mailbox. Implement exclusive ownership and test-run attribution in your harness.

Your harness owns allocation, locking, retry coordination, attribution checks, and release policy. Use the developer overview for the API integration layer, while keeping those decisions in your test infrastructure. Your application remains responsible for challenge validation, expiry, and single-use behavior.

Do not build tests around a promise of immediate delivery. Mailera does not guarantee Inbox placement; when a candidate is missing, inspect placement rather than assuming every message will be in Inbox.

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