PNTR TestKit

Deterministic email and webhook tests

Wait for the exact email or webhook your Playwright, Cypress, or CI run triggered.

Unique test recipient flowing through a PNTR inbox to an OTP assertion
One recipient per test keeps parallel tests isolated.

Your test waits once. PNTR watches.

Replace repeated inbox downloads and fixed sleeps with one filtered request that resolves when a matching event arrives.

Match the exact email

Filter by recipient, sender, subject, and the time your test started.

Match the exact request

Filter by method, path, header, body content, and a freshness boundary.

Fail on a real timeout

PNTR holds the wait on the server and returns HTTP 408 when nothing matches.

Use it inside the test you already run

Use the CLI in a shell step, or call PNTR's authenticated wait endpoints from any runner that can send HTTP requests.

Wait endpoint/HTTP
POST /api/subdomains/:id/emails/wait
CLI waits in CI/Shell
recipient="$(pntr recipient testbox.pntr.dev \  --prefix signup)"pntr email wait "$PNTR_EMAIL_SUBDOMAIN_ID" \  --to "$recipient" \  --subject "Verification code" \  --since 5m \  --timeout 20s \  --jsonpntr webhook wait "$PNTR_WEBHOOK_SUBDOMAIN_ID" \  --method POST \  --path /stripe \  --body-contains "payment_intent.succeeded" \  --since 5m \  --timeout 20s \  --json
Wait for a signup email/TypeScript
import { randomUUID } from "node:crypto";const startedAt = new Date().toISOString();const recipient =  `signup-${randomUUID()}@testbox.pntr.dev`;// Capture startedAt before the browser action triggers the email.await page.getByLabel("Email").fill(recipient);await page.getByRole("button", { name: "Sign up" }).click();const response = await request.post(  `https://api.pntr.dev/api/subdomains/${    process.env.PNTR_EMAIL_SUBDOMAIN_ID  }/emails/wait`,  {    headers: {      Authorization: `Bearer ${process.env.PNTR_TOKEN}`,    },    data: {      recipient,      subject_contains: "Verification code",      since: startedAt,      timeout_seconds: 20,    },    timeout: 30_000,  });expect(response.ok()).toBeTruthy();const message = await response.json();const content = message.body_text ?? message.body_html ?? "";const otp = content.match(/\b\d{6}\b/)?.[0];
Wait for a Stripe webhook/TypeScript
const startedAt = new Date().toISOString();// Trigger the provider's test-mode delivery after recording startedAt.await triggerTestPayment();const response = await fetch(  `https://api.pntr.dev/api/subdomains/${    process.env.PNTR_WEBHOOK_SUBDOMAIN_ID  }/requests/wait`,  {    method: "POST",    headers: {      Authorization: `Bearer ${process.env.PNTR_TOKEN}`,      "Content-Type": "application/json",    },    body: JSON.stringify({      method: "POST",      path: "/stripe",      body_contains: "payment_intent.succeeded",      since: startedAt,      timeout_seconds: 20,    }),  });if (!response.ok) throw new Error("Webhook did not arrive");const delivery = await response.json();expect(delivery.body_truncated).toBe(false);

One test run, two isolated endpoints

pntr env create provisions -mail and -hook sibling subdomains. Each environment uses two subdomain quota slots.

Environment lifecycle/Shell
pntr env create "ci-$GITHUB_RUN_ID" \  --output .pntr-test-env.json \  --jsonpntr env delete \  --manifest .pntr-test-env.json \  --confirm \  --json

[email protected]

Isolate concurrent signup, OTP, and reset-password tests

Every local part reaches the catch-all inbox. Recipient and since filters make each run select only its own message.

POST ci-8421-hook.pntr.dev/stripe

Assert the delivery your action caused

Match a webhook by path, header, method, body content, and start time.

Put TestKit into a real workflow

Add one deterministic wait to your next test

Free includes 2 parallel waits. Premium includes 10.

Open dashboard