Skip to content

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.

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.

Terminal window
reflow init Browser Mailbox
chmod 600 /absolute/path/to/bindings.json
reflow target https://your-test-app.example.com --group account-tests --collection <collection-id> --scope shared --bindings /absolute/path/to/bindings.json

Planning reads declarations and retained evidence. It neither creates inboxes nor waits for mail. The address command provisions the named inbox during apply.

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 code
providers:
ui: {use: Browser, binding: site}
inbox: Mailbox
plugins:
- name: Browser
sourceCommit: 5852a91f8ba9f4c73585a5984a96117a159c068f
descriptorDigest: c86d7af94675fed13771b48b5f75f60a4988a2cf4158dd6d211e0f651006e505
- name: Mailbox
sourceCommit: 5852a91f8ba9f4c73585a5984a96117a159c068f
descriptorDigest: 2d402988bb9d66c4014144e96756627fd03a12675031d52b7e5d884ced7e1307
---
### Choose the account
```rfl
capture email = inbox.address name="signup"
```
### Request and use a new code
```rfl
open /login
fill 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=30
fill 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=30
open ${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.

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.

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.

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.

CommandArgumentsReturns
inbox.addressRequired name string.The address as a string; provisions it during apply if needed.
inbox.cursorRequired name string.An integer cursor; does not allocate an inbox.
inbox.wait_codeRequired name, from, subject strings and after integer; optional timeout integer.One matching code as a string.
inbox.wait_linkRequired 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.