Mailbox plugin
Use a named Mailbox plugin connection for signup, magic-link and OTP journeys.
The remote inbox keeps the same address across plans, applies and partial reruns
until you reset the selected execution group’s test data. Flows in the same group
can share a name such as signup; separate names give them separate inboxes.
This plugin and the reset lifecycle require matching Reflow CLI/MCP, API and provider builds. Updating the skill alone does not add them to an older release. The mailbox service also needs a configured delivery path: a local synthetic HTTP ingress tests your application’s mail integration, while external SMTP requires a configured inbound transport. Use the returned address rather than assuming a public mail domain.
Connect the remote mailbox
Section titled “Connect the remote mailbox”Choose an application collection during onboarding. Use its ID when selecting the target below.
Keep the service credentials in a private bindings file, alongside your other plugin connections. Replace these example values and keep the file out of Git:
{ "site": {"provider": "Browser", "configuration": {}}, "inbox": { "provider": "Mailbox", "configuration": { "apiUrl": "https://app.reflow.io", "teamId": "your-team-id", "token": "your-private-run-scoped-PAT" } }}The token needs run scope and access to the selected team. The plugin sends it
only to the configured HTTPS origin; HTTP is supported for loopback development.
Mailbox credentials belong in this configuration, never in a flow or captured
output. The connection currently uses an explicit token; it does not inherit the
sidecar’s login automatically.
reflow init Browser Mailboxchmod 600 /absolute/path/to/bindings.jsonreflow target https://your-test-app.example.com --group account-tests --collection <collection-id> --scope shared --bindings /absolute/path/to/bindings.jsonPlanning reads declarations and retained evidence. It neither creates inboxes nor
waits for mail. The address command provisions the named inbox during apply.
Receive a code as an ordinary variable
Section titled “Receive a code as an ordinary variable”This flow assumes the application has a test account for the named address. For
signup, use the returned email in your account-creation step first; repeating
signup against an existing address needs your application’s usual idempotent setup
or an explicit reset.
---name: Sign in with an email codeproviders: ui: {use: Browser, binding: site} inbox: Mailboxplugins: - name: Browser sourceCommit: 5852a91f8ba9f4c73585a5984a96117a159c068f descriptorDigest: c86d7af94675fed13771b48b5f75f60a4988a2cf4158dd6d211e0f651006e505 - name: Mailbox sourceCommit: 5852a91f8ba9f4c73585a5984a96117a159c068f descriptorDigest: 2d402988bb9d66c4014144e96756627fd03a12675031d52b7e5d884ced7e1307---
### Choose the account
```rflcapture email = inbox.address name="signup"```
### Request and use a new code
```rflopen /loginfill label="Email" ${email}capture before = inbox.cursor name="signup"click role=button name="Send code"capture code = inbox.wait_code name="signup" after=${before} from="noreply@" subject="Sign-in code" timeout=30fill label="Verification code" ${code}click role=button name="Verify"expect page url~="/account"```Keep the cursor capture and the action that sends email in the same step. Reflow reruns a step as a unit: a new send then gets a new cursor, excluding mail from the earlier attempt. Splitting the cursor into a reused earlier step can select an old message. The sender and subject filters are required; an ambiguous match fails instead of choosing a code or link for you.
For a magic link, capture a link and open it using the ordinary browser command:
capture link = inbox.wait_link name="signup" after=${before} from="noreply@" subject="Sign in" timeout=30open ${link}The provider returns declared scalar values. email, before, code and link
use the same capture and reference rules as other variables. Captures become
retained, team-visible execution evidence; use disposable test accounts.
Share accounts across flows
Section titled “Share accounts across flows”A producer flow can capture its address and export it like any other output:
capture email = inbox.address name="customer"A consumer lists the producer under needs, declares
needs.create-account.outputs.email as a string reference, then reads
${needs.create-account.outputs.email}. The plan binds that reference to the exact
successful producer result. See the complete producer and consumer example.
Mailbox names are shared within the selected team and execution group, not scoped
to a flow or apply. Shared groups agree across devices; device groups remain
separate. Team members with run access can access named mailboxes in either scope.
Selecting another target in the same group keeps its named test data;
use a different group for a different environment, or reset deliberately. Parallel
flows using one inbox can interleave messages. Give independent
journeys different names or order them with needs.
Reset and message retention
Section titled “Reset and message retention”Use reflow reset to replace the selected group’s named inboxes and generated values. Taint reruns checks with the same address. Reset does not delete accounts or other data in your application.
Messages are readable for up to 24 hours, or the service’s shorter configured period, independently of the address. Expiry does not replace the inbox or rewind its cursor. The recurring cleanup worker removes expired bodies after a two-minute grace period; expiry is not a promise of immediate physical deletion.
Existing browser-run mailboxes
Section titled “Existing browser-run mailboxes”The separate browser-run/device-job path still supplies ${MAILBOX} when mailbox
provisioning is requested, and retains its run-scoped email commands and remote
MCP mailbox tools. Those disposable run mailboxes do not have this named,
stable-until-reset lifecycle. Use the Mailbox provider with plan / apply for
shared test data.
Commands
Section titled “Commands”| Command | Arguments | Returns |
|---|---|---|
inbox.address | Required name string. | The address as a string; provisions it during apply if needed. |
inbox.cursor | Required name string. | An integer cursor; does not allocate an inbox. |
inbox.wait_code | Required name, from, subject strings and after integer; optional timeout integer. | One matching code as a string. |
inbox.wait_link | Required name, from, subject strings and after integer; optional timeout integer. | One matching HTTP(S) link as a string. |
Mailbox wait timeouts use seconds, from 1 to 30, with a default of 30. Both sender and subject are substring filters. No matching message before the deadline, or an ambiguous code or link, fails the command. Use separate names for parallel journeys instead of widening filters until an old message matches.
Read test accounts and shared data for application setup, reset behavior and the distinction between named test values and observed data.
Flows using this plugin can be saved in Reflow or in your repository. Read test storage for the team preference, private bindings and the shared plan/apply workflow.