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. Install the current CLI and refresh the flow’s Mailbox source and
descriptor pins after inspecting its schema. Earlier pinned providers expose
wait_code / wait_link; this version replaces those with capture and extract.
Updating the skill alone does not add commands 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; the service determines its 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 --collection <collection-id>chmod 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: Mailbox---
### Choose the account
```rflinbox.address name="signup" -> email```
### Request and use a new code
```rflopen /loginfill label="Email" ${email}inbox.cursor name="signup" -> beforeclick role=button name="Send code"inbox.capture name="signup" after=${before} from="noreply@" subject="Sign-in code" timeout=30 -> email_bodyinbox.extract text=${email_body} pattern="code is ([0-9]{6})" group=1 -> codefill 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
message match fails instead of choosing one for you. inbox.capture returns the
plain-text body, including line breaks, into the variable after ->. It does not
look for codes or links. inbox.extract reads that variable and applies the regex
you wrote. In this example the email must contain text such as Your code is 831204.
For an email containing Sign in: https://your-app.example/verify?token=...,
extract the link from its body, then open it:
inbox.capture name="signup" after=${before} from="noreply@" subject="Sign in" timeout=30 -> email_bodyinbox.extract text=${email_body} pattern="Sign in: (https://your-app[.]example/verify[?]token=[A-Za-z0-9_-]+)" group=1 -> linkopen ${link}Choose any valid variable names after ->; ${email_body} refers to the saved
body and ${code} to the extracted value. Each command is explicit and retains its
result. Plugin result bindings use the same typed references and cross-flow output
rules as other variables. Both email bodies and extracted values 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:
inbox.address name="customer" -> emailA 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.capture | Required name, from, subject strings and after integer; optional timeout integer. | The plain-text body of exactly one matching message, up to 64 KiB. |
inbox.extract | Required text and pattern strings, and group integer. | One regex match from the supplied text; no mailbox request. |
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 more than one matching message, fails the command. Use separate names for parallel journeys instead of widening filters until an old message matches.
Extraction uses Go RE2 regex syntax. Choose group=0 for the whole match or
group=1 for the first parenthesized group. The group is required. No match, more
than one match (even if the values are identical), a missing group or an empty
selected group fails. Patterns are limited to 4096 bytes and text to 64 KiB.
Lookaround and backreferences are unsupported. Use [0-9] to avoid escaping; in
quoted RFL arguments a regex backslash must be doubled, for example "\\d{6}".
For a link, include the expected application origin in the regex when appropriate;
extraction does not infer a URL or validate its destination.
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.