InboxSpecDocumentation

Testing

OTP and link assertions

Correlate verification codes and reset links with the exact application journey.

OTP login, correlation and bounded waits

This SDK recipe uses application-specific trigger and completion functions. Set a total test deadline longer than the wait and its transport margin. Configure the application to send through the fixture domain’s MX route: authenticated Capture SMTP files mail under separate capture inboxes.

Example
import { InboxSpec } from "@inboxspec/sdk";
const client = new InboxSpec({
  apiKey: process.env.INBOXSPEC_API_KEY!,
  baseUrl: process.env.INBOXSPEC_API_URL!,
});
const runId = "ci.4182.a1.w1"; // Supply your run, attempt and worker IDs.
const correlationId = runId + ".signup";
try {
  const setup = await client.fixtures.ensure({
    run_id: runId,
    manifest: { version: 1, fixtures: [
      { name: "signup", retention_seconds: 600 },
    ] },
  });
  if (setup.status !== "ready") throw new Error("Fixture setup failed");
  const inbox = setup.fixtures.find((f) => f.name === "signup")!.inbox!;
  const since = new Date().toISOString();
  // Your application sends X-InboxSpec-Correlation-ID: correlationId.
  await triggerYourApplication(inbox.address, correlationId);
  const outcome = await client.messages.wait({
    inbox: inbox.id,
    match: { since, correlation_id: correlationId, has_code: true },
    request_id: "wait-" + correlationId,
    timeout_ms: 60_000,
  });
  if (outcome.status === "timeout") throw new Error(outcome.reason);
  const code = outcome.message.codes[0];
  if (!code) throw new Error("No verification code");
  await completeYourApplicationFlow(code);
} finally {
  const cleanup = await client.fixtures.cleanup(runId);
  if (cleanup.status !== "complete") throw new Error("Cleanup incomplete");
}

If the sender cannot set correlation headers, use a fresh inbox per journey and narrow with since, subject_contains and from_contains. to_contains matches headers and may omit BCC recipients; the selected inbox is the envelope-delivery boundary. correlation_id exactly matches X-InboxSpec-Correlation-ID, case-sensitively. For password reset or verification links, use has_link_kind with reset_password or verify and validate the application origin before following a link.

Timeouts return HTTP 200 with status: timeout and reason: no_arrivals or no_matching_arrivals. Check the hint and arrival count. Authentication, quota, concurrency and infrastructure errors remain separate. Responses include started_at, completed_at, waited_ms and request_id; matched mail includes received_at, measured at SMTP DATA completion.

If workspace receiving is blocked and no stored message matches, waits return HTTP 402 with organisation_limit_exceeded. For an exhausted message allowance, read details.used, limit and period_resets_at. Pause the sending batch and review workspace usage before retrying. Existing mail remains readable; deleting it does not reset monthly usage. Before creating test accounts, use GET /v1/usage with usage:read scope to inspect the effective organisation allowance and extra-usage settings.

Request IDs trace attempts; they are not durable replay keys. Waits do not consume messages, so repeated waits can return the same oldest match. Use a fresh namespace for a new test attempt and reuse a run only for setup retries. Avoid polling loops. Honor retry guidance within one deadline; use SDK signal or CLI Ctrl+C to cancel.

Example
inboxspec fixtures cleanup --run ci.4182.a1.w1 --json

Run cleanup after producers and waits finish, in finally or a CI always() step. It uses both the run prefix and exact reserved run label, preventing deletion of nested runs. Incomplete cleanup is nonzero and can be resumed with the same run ID. Expiry is a fallback; physical removal may lag. Cleanup does not reset usage or delete accounting history.