Parallel browser runs should use explicit inbox ownership and application-level attempt tracking so each retrieved email code is associated with the intended login. Lumbox can provide separate inboxes and retrieval filters, but its OTP endpoint does not reserve or consume a code for a particular run.
The difficult case is two valid logins with similar messages. Imagine two authorized workflows using the same portal, both waiting for a recent email from the same sender. A sender filter and a short time window may match both. The newest email is not necessarily the one that belongs to whichever worker happens to ask first.
This is a design example, not a reported customer incident. It shows why a useful inbox API still needs a clear ownership model in the application around it.
Choose the lifetime of the inbox
An inbox can belong to an agent, a customer account or a short-lived run. Each choice changes how you onboard users, keep account addresses stable and manage concurrent work. There is no universal rule that every login should create a fresh address.
| Inbox scope | Useful when | Main design cost |
|---|---|---|
| Per run | A controlled workflow can choose its destination each time | Provisioning, cleanup and active inbox limits |
| Per customer account | A portal or forwarding rule needs a stable address | Coordinate overlapping attempts on that account |
| Per agent | An agent owns a persistent email identity | Separate unrelated customer workflows explicitly |
| Shared across workflows | Existing routing forces one mailbox | Stronger correlation and scheduling logic |
For an existing customer portal, a stable customer-account inbox may be easier to configure than a new address for every run. If two runs must use that same account, serialize the authentication step or implement another reliable correlation mechanism. An address label alone cannot distinguish simultaneous attempts.
Record the attempt before requesting mail
Create an application record before the browser asks the portal to send a code. Include the expected customer account, portal origin, browser session, inbox ID and start timestamp. A minimal application-owned structure might look like this:
{
"attempt_ref": "local-attempt-example",
"customer_ref": "local-customer-example",
"portal_origin": "https://portal.example.com",
"inbox_ref": "returned-inbox-id",
"started_at": "runtime-generated-ISO-timestamp",
"state": "waiting_for_email"
}
This is a design sketch, not a Lumbox API response. Store actual references from your application and runtime. Do not invent a run_id query parameter and expect the email service to correlate a message that never contained it.
Once retrieval returns an email_id, associate that result with the attempt record. Keep the sensitive code out of routine logs. The message ID is useful for investigating which message was selected without displaying the authentication value everywhere.
Use freshness filters with realistic expectations
The REST OTP route accepts since, which should normally come from the start of the relevant attempt. Without it, the endpoint considers the previous five minutes. The route also accepts a received-sender substring filter. See the API reference.
These filters narrow the search. They do not establish an exclusive claim on a message. Two workers with the same inbox and overlapping windows can receive the same result. The route returns the first extracted code from the newest suitable verification message; it is not an event queue with acknowledgment semantics.
Forwarding complicates sender matching further. The received address may identify the forwarder. Verify it using an actual forwarded message before choosing a filter. Matching a string such as a domain fragment should never be presented as authentication of the original sender.
Keep the browser attempt current
Before entering a returned value, check that the browser is still waiting on the expected challenge. A timeout, page refresh or second request can change the relevant state. If your application has already canceled the attempt, discard the late result instead of typing it into whichever page is currently open.
The same reasoning applies when a portal sends multiple codes. The portal determines which values remain valid. Lumbox can return parsed content and an expiry inferred from message wording, but it cannot confirm that a code is still accepted for the active session.
Use one application owner for the authentication step. A second worker should not silently take over the same browser session while the first worker is still waiting for mail. If you implement a lock, define its expiry and recovery behavior so a crashed worker does not block the account indefinitely.
Separate access restrictions from correlation
An agent should only be able to access the inboxes it needs. The repository includes an endpoint for minting an inbox-scoped key and checks allowed inbox IDs on the OTP route. That can restrict which inbox a key reads; it is different from deciding which attempt owns a message inside that inbox.
The current minting handler returns a key with an inbox restriction and wildcard scopes. Do not describe that key as automatically read-only. Review the permission surface you expose to a runtime and test both allowed and denied operations. The inbox documentation and implementation are the starting points.
For a pilot, you can keep service access in a backend adapter that owns the key and returns only the retrieval result needed by the browser workflow. This is an application architecture choice, not an extra permission feature asserted for Lumbox.
Test overlap deliberately
Run a controlled test where two attempts start close together and messages arrive in the opposite order. Confirm that each workflow either selects the correct message or pauses because the route is ambiguous. A test that always runs one login at a time will not reveal an overlap bug.
Then repeat with a delayed message from a previous attempt. Test a repeated retrieval request and a canceled browser run. Your expected result should include what the application refuses to do, such as entering a code after the attempt is complete.
Keep inbox capacity in the design. The free Lumbox plan allows three active inboxes, while paid plans have larger limits. Creating an inbox per run can consume that capacity if cleanup is missing. Deleting an inbox also changes future delivery, so lifecycle management must match how the portal or forwarding rule uses the address.
The core implementation references are apps/api/src/routes/inboxes.ts for creation, scoped keys and OTP retrieval, plus packages/plans/src/index.ts for limits. Checked October 3, 2026. For a first test, use two owned workflows and prove the correlation behavior before adding more concurrency.
Kumar