What makes an email OTP stale?
Your test requests a verification code, finds an email, and submits the wrong code. The fix is not simply “sort newest first.” You need two separate controls: selecting an email for the active attempt and validating the challenge at the issuing application.
Here, a verification attempt means your application's record of one verification workflow. A challenge is the issuer's verification state. Keep them separate: requesting another email does not, by itself, tell you whether the issuer created a new challenge.
Treat a candidate as stale or unsuitable when it belongs to an earlier attempt, its challenge has expired, a replacement challenge has superseded it, or it has already been consumed. For resend behavior, follow the issuer's documented policy rather than assuming every resend invalidates the previous code.
| Control | What it should establish | What it cannot establish alone |
|---|---|---|
| Email selection | Intended mailbox and expected message | Whether the challenge remains valid |
| Local submission safeguards | Active attempt, deadline, no duplicate handling | Server-side replay protection |
| Issuer validation | Correct account, purpose, challenge, expiry, and unused state | Whether your retrieval logic selected the intended email |
Don't apply authenticator timing rules to email codes. RFC 6238 defines TOTP using a time-derived value and time step. That definition does not supply a universal lifetime for email OTPs.
Use the workflow below only with an existing mailbox and application you own or are authorized to test. For basic mailbox retrieval, start with reading OTP emails from your own inbox.
How do you retrieve a candidate with Mailera?
Mailera connects existing mailboxes; it does not create test mailboxes or email addresses. Choose the mailbox before starting verification, rather than searching across unrelated accounts after a code request.
- Identify the connected test mailbox and keep its ID in your application state.
- Create an application-owned attempt identifier, fixed start time, and polling deadline before triggering the email. Record the intended account and verification purpose too.
- Store the issuer's challenge identifier when the application exposes one.
- Request the code, then retrieve a candidate using a read-scoped API key.
In Settings, open the API key tab and select Create key. Set Mailbox scope to the test mailbox group and select Read, without sending or management permissions.

The getOtp operation, GET /mailboxes/{id}/otp, retrieves the latest OTP code and accepts from, subject, and since_minutes. With $MAILERA_API_KEY and $MAILBOX_ID set in your private shell, a request looks like this:
curl --get "https://mailera.app/api/public/v1/mailboxes/$MAILBOX_ID/otp" \
--header "Authorization: Bearer $MAILERA_API_KEY" \
--data-urlencode "from=verification@example.test" \
--data-urlencode "subject=Verify your email" \
--data-urlencode "since_minutes=5"Replace the example sender and subject with those of your authorized test application. The 5 is an illustrative parameter value, not an OTP lifetime.
Preserve the original attempt state, including its fixed start time, across polling retries. Require correlation with the active challenge before submission. The Mailera API documentation covers the endpoints; the developer overview covers integration options.
For closer inspection, GET /mailboxes/{id}/messages supports since and from, alongside folder selection. Message results include id, subject, received_at, and sender details. Use the selected message's ID as $MESSAGE_ID to retrieve its content:
curl "https://mailera.app/api/public/v1/mailboxes/$MAILBOX_ID/messages/$MESSAGE_ID" \
--header "Authorization: Bearer $MAILERA_API_KEY"The message body includes body.text and body.html. These let you inspect application-specific correlation information when your own verification email contains it. Neither a timestamp nor extracted code replaces that correlation. If you cannot distinguish the active challenge from earlier ones, stop instead of submitting whichever candidate looks newest.
Which checks belong in your application?
Prefer an issuer-provided challenge identifier when your owned system exposes one. Sender and subject filters help select messages; don't use them as sender authentication or proof that an email belongs to the active challenge.
- Load the active attempt; stop if it is canceled or past its polling deadline.
- Retrieve a candidate from the designated mailbox.
- Require sufficient correlation with the expected account, purpose, and challenge; stop on ambiguity.
- Apply the attempt's freshness rules and any issuer-provided expiry information.
- Atomically claim candidate handling within the attempt so another worker cannot submit it simultaneously.
- Recheck that the attempt is active, submit to the issuer, and record the outcome. Preserve an unknown outcome as unknown.
Why receipt time is insufficient
Consider a test where attempt A creates a challenge, then a resend creates replacement challenge B. B's email arrives first. A's delayed email arrives afterward. Selecting by arrival order would choose A even though B is the active challenge.

Microsoft Graph's message resource distinguishes receivedDateTime, when the message was received, from sentDateTime, when it was sent. Neither definition describes OTP expiry or challenge state. Don't turn a recent receipt timestamp, or a sender's Date header, into an expiry decision.
Track candidate handling per attempt using a stable message identity where available. Don't assume every provider identifier survives a folder move: Microsoft Graph documents that its default message ID changes when a message moves between containers. Keep your application-owned handling record separate from provider identifiers.
Avoid a permanent global set of code strings. Make deduplication attempt-scoped so the same short value in a later challenge does not inherit an unrelated rejection.
What the verifier must enforce
For a backend you own, bind verification to the intended account, purpose, and challenge. Enforce the deadline at the server and atomically consume the challenge on success. Local test-run bookkeeping should prevent accidental submissions, but it cannot replace server-side single-use enforcement.
For an external issuer, follow its documented flow and response. If you cannot establish which challenge to use, stop or begin a new supported attempt. Don't cycle through old candidates until one succeeds.
How should retries and resends work?
Give polling a fixed deadline and backoff between requests. Keep the polling budget separate from the issuer's expiry policy: allowing a test to wait longer must not extend the challenge's validity.
Preserve the original attempt state across retries and require every candidate to correlate with the active challenge before submission.
Handle resend according to the issuer's policy:
- Replacement challenge: switch the active challenge, cancel pending work for the old attempt, and reject late results associated with it.
- Reused challenge: retain the appropriate challenge state and follow the issuer's deadline and reuse rules. Don't invent a new expiry because another email was sent.
Canceling a worker is not enough on its own. Require an active-attempt check immediately before submission so a late retrieval result cannot continue under superseded state.
If submission times out, don't assume the code remains unused. Reconcile the result through the application's supported state check, or restart through its supported verification flow. Avoid automatic resubmission while the outcome is unknown.
Keep concurrent-run isolation separate from freshness logic; see parallel OTP tests without mailbox mix-ups.
What common mistakes should your tests catch?
Build these cases into your owned application's test suite.
| Setup | Expected retrieval or local behavior | Expected verifier result |
|---|---|---|
| Previous-run email remains among the candidates | Reject it unless it correlates with the active challenge | Reject a mismatched challenge |
| A's email arrives after replacement challenge B | Don't select A because it arrived last | Reject A if the resend policy invalidates it |
| B arrives before A | Preserve challenge order independently of delivery order | Evaluate B against active challenge state |
| Two workers handle the same candidate | Only one worker claims submission | Permit at most one successful consumption |
| Email arrives after challenge expiry | Don't treat recent receipt as renewed validity | Reject the expired challenge |
| A successfully consumed code is retrieved again | Skip already-handled work | Reject reuse of the consumed challenge |
| Matching emails cannot be correlated | Stop rather than choose arbitrarily | Make no submission |
| Submission succeeds but its response is lost | Preserve an unknown local outcome and reconcile | Do not permit another successful consumption |
Test expiry boundaries with your owned backend's controlled clock rather than arbitrary sleeps. Check the instant before expiry, the boundary itself according to your policy, and the instant after it. Test message-clock differences separately so a receipt-time change cannot alter server-side validity.
For resends that reuse a challenge, change the expected result to match that policy. The test should enforce your issuer's contract, not a blanket “resend invalidates everything” rule.
A missing email and a stale email are different failures. If no suitable message appears, use the verification-email Junk checklist rather than relaxing freshness checks.
How do you keep OTP retrieval safe?
Keep the read-only key scoped to the test mailbox group and out of browser code. Don't include keys, codes, addresses, tokens, or email bodies in logs or screenshots. Log rejection reasons and non-sensitive attempt metadata instead. Review Mailera's security information when setting access boundaries.
If an AI agent reads the email, treat its content as untrusted data, not instructions. Don't let a message change tool permissions, trigger unrelated actions, or direct the agent to follow links. Require confirmation before sending email. The browser-agent verification workflow addresses pausing and resuming around this step.
Mailera retrieves the latest OTP code. For a verifier you own, enforce challenge expiry and single use at the issuer. Mailera does not guarantee inbox placement. Stop safely when the active attempt cannot be established.
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








