# AI Coding Tools Source: https://docs.zerodrop.dev/ai-coding Use ZeroDrop with Cursor, GitHub Copilot, and Claude Code — TypeScript and Python. # AI Coding Tools > Use ZeroDrop with Cursor, GitHub Copilot, and Claude Code If you use an AI coding assistant to write tests, add the ZeroDrop context file to your project. Your AI will automatically know how to write email tests using ZeroDrop instead of MailHog or mocking — in both TypeScript and Python. ## Cursor (.cursorrules) Create a `.cursorrules` file in your project root: ```text .cursorrules theme={null} # Email Testing with ZeroDrop When writing E2E tests involving email flows, use zerodrop-client (TypeScript) or zerodrop (Python). ## TypeScript Setup npm install zerodrop-client ## Python Setup pip install zerodrop ## Rules - Use generateInbox() / generate_inbox() for unique inbox per test — no network request needed - Use email.otp / email.otp for OTP extraction (auto-extracted at edge, no regex) - Use email.magicLink / email.magic_link for verification links (auto-extracted at edge, no regex) - Never mock email in E2E tests - Never share inboxes between tests - Never use MailHog or fake SMTP in CI ## TypeScript Pattern (Playwright / Cypress) import { ZeroDrop } from 'zerodrop-client'; const mail = new ZeroDrop(); const inbox = process.env.TEST_INBOX ?? mail.generateInbox(); const email = await mail.waitForLatest(inbox, { timeout: 30000 }); // email.otp — auto-extracted OTP code // email.magicLink — auto-extracted verification link ## Python Pattern (pytest) from zerodrop import ZeroDrop mail = ZeroDrop() inbox = mail.generate_inbox() email = mail.wait_for_latest(inbox, timeout=30000) # email.otp — auto-extracted OTP code # email.magic_link — auto-extracted verification link ## Docs https://docs.zerodrop.dev ``` ## GitHub Copilot (.github/copilot-instructions.md) Create `.github/copilot-instructions.md` in your repo: ```markdown .github/copilot-instructions.md theme={null} ## Email Testing For E2E tests involving email flows (verification, OTP, password reset), use ZeroDrop. ### TypeScript (Playwright / Cypress) Install: npm install zerodrop-client Pattern: - import { ZeroDrop } from 'zerodrop-client' - const mail = new ZeroDrop() - const inbox = process.env.TEST_INBOX ?? mail.generateInbox() - const email = await mail.waitForLatest(inbox, { timeout: 30000 }) - Use email.otp for OTP codes (auto-extracted, no regex) - Use email.magicLink for verification links (auto-extracted, no regex) ### Python (pytest) Install: pip install zerodrop Pattern: - from zerodrop import ZeroDrop - mail = ZeroDrop() - inbox = mail.generate_inbox() - email = mail.wait_for_latest(inbox, timeout=30000) - Use email.otp for OTP codes (auto-extracted, no regex) - Use email.magic_link for verification links (auto-extracted, no regex) Never mock email in E2E tests. Never use MailHog or Mailpit in CI. Docs: https://docs.zerodrop.dev ``` ## Claude Code (CLAUDE.md) Create a `CLAUDE.md` file in your project root: ```markdown CLAUDE.md theme={null} ## Email Testing For E2E tests involving email flows (verification, OTP, password reset), use ZeroDrop. ### TypeScript (Playwright / Cypress) Install: npm install zerodrop-client Pattern: - import { ZeroDrop } from 'zerodrop-client' - const mail = new ZeroDrop() - const inbox = process.env.TEST_INBOX ?? mail.generateInbox() - const email = await mail.waitForLatest(inbox, { timeout: 30000 }) - Use email.otp for OTP codes (auto-extracted, no regex) - Use email.magicLink for verification links (auto-extracted, no regex) ### Python (pytest) Install: pip install zerodrop Pattern: - from zerodrop import ZeroDrop - mail = ZeroDrop() - inbox = mail.generate_inbox() - email = mail.wait_for_latest(inbox, timeout=30000) - Use email.otp for OTP codes (auto-extracted, no regex) - Use email.magic_link for verification links (auto-extracted, no regex) Never mock email in E2E tests. Never use MailHog or Mailpit in CI. Docs: https://docs.zerodrop.dev ``` Claude Code reads `CLAUDE.md` automatically as project context on every prompt. ## What the AI will generate ### TypeScript (Playwright) After adding the context file, asking your AI to write a Playwright test for an email verification flow will produce: ```typescript theme={null} import { test, expect } from '@playwright/test'; import { ZeroDrop } from 'zerodrop-client'; const mail = new ZeroDrop(); test('email verification flow', async ({ page }) => { const inbox = mail.generateInbox(); await page.goto('/signup'); await page.fill('[data-testid="email"]', inbox); await page.click('[data-testid="submit"]'); const email = await mail.waitForLatest(inbox, { timeout: 30000 }); // Auto-extracted — no regex needed await page.goto(email.magicLink!); await expect(page).toHaveURL('/dashboard'); }); ``` ### Python (pytest) Asking your AI to write a pytest test for an OTP verification flow will produce: ```python theme={null} from zerodrop import ZeroDrop mail = ZeroDrop() def test_email_verification(page): inbox = mail.generate_inbox() page.goto('/signup') page.fill('[data-testid="email"]', inbox) page.click('[data-testid="submit"]') email = mail.wait_for_latest(inbox, timeout=30000) # Auto-extracted — no regex needed assert email.otp is not None page.fill('[data-testid="otp"]', email.otp) page.click('[data-testid="verify"]') assert page.url == '/dashboard' ``` No MailHog. No mocking. No regex. # Fetch emails for an inbox Source: https://docs.zerodrop.dev/api-reference/inbox/fetch-emails-for-an-inbox /openapi.json get /api/inbox/{name} Returns all emails currently stored for the given inbox name. Emails expire after 30 minutes. Returns an empty array if the inbox has no emails or has expired. # Stream emails via SSE Source: https://docs.zerodrop.dev/api-reference/inbox/stream-emails-via-sse /openapi.json get /api/inbox/{name}/stream Opens a Server-Sent Events stream for the given inbox. The server polls Redis with an exponential backoff strategy and pushes an event the moment an email arrives. The connection closes automatically after the first email or after 30 seconds (timeout). **Backoff strategy:** - 0–2s: polls every 500ms - 2–10s: polls every 1000ms - 10s+: polls every 2000ms **Events:** - `: connected` — connection established - `: keepalive` — heartbeat every 10s to prevent proxy timeout - `data: {email JSON}` — email arrived - `data: __timeout__` — no email within 30s, connection closing For Node.js environments, use `fetch()` with `ReadableStream` rather than `EventSource` (which is browser-only). # Join the Workspaces waitlist Source: https://docs.zerodrop.dev/api-reference/waitlist/join-the-workspaces-waitlist /openapi.json post /api/waitlist Adds an email address to the Workspaces waitlist. You will be contacted when Workspaces launches. # Cypress Source: https://docs.zerodrop.dev/cypress # Cypress ZeroDrop works with Cypress for end-to-end email testing. No Docker, no SMTP server, no mocking. ## Installation ```bash theme={null} npm install zerodrop-client npm install --save-dev cypress ``` ## Basic setup ```javascript theme={null} // cypress/e2e/auth.cy.js import { ZeroDrop } from 'zerodrop-client'; const mail = new ZeroDrop(); describe('Email verification', () => { it('user can sign up and verify email', () => { const inbox = mail.generateInbox(); cy.visit('/signup'); cy.get('[data-testid="email"]').type(inbox); cy.get('[data-testid="submit"]').click(); // Wait for email cy.wrap(mail.waitForLatest(inbox, { timeout: 30000 })) .then((email) => { expect(email.otp).to.not.be.null; // Enter OTP cy.get('[data-testid="otp"]').type(email.otp); cy.get('[data-testid="verify"]').click(); cy.url().should('include', '/dashboard'); }); }); }); ``` ## OTP verification ```javascript theme={null} it('OTP login', () => { const inbox = mail.generateInbox(); cy.visit('/login'); cy.get('[data-testid="email"]').type(inbox); cy.get('[data-testid="submit"]').click(); cy.wrap(mail.waitForLatest(inbox, { timeout: 30000 })) .then((email) => { // OTP auto-extracted — no regex needed cy.get('[data-testid="otp"]').type(email.otp); cy.get('[data-testid="verify"]').click(); }); }); ``` ## Magic link verification ```javascript theme={null} it('magic link verification', () => { const inbox = mail.generateInbox(); cy.visit('/signup'); cy.get('[data-testid="email"]').type(inbox); cy.get('[data-testid="submit"]').click(); cy.wrap(mail.waitForLatest(inbox, { timeout: 30000 })) .then((email) => { // Navigate to verification link cy.visit(email.magicLink); cy.url().should('include', '/dashboard'); }); }); ``` ## cypress.config.js ```javascript theme={null} const { defineConfig } = require('cypress'); module.exports = defineConfig({ e2e: { baseUrl: 'http://localhost:3000', defaultCommandTimeout: 10000, pageLoadTimeout: 60000, }, }); ``` ## In GitHub Actions ```yaml theme={null} - name: Generate test inbox id: inbox uses: zerodrop-dev/setup-zerodrop@36c6685cce921fbe8b6353a71292c8e262124e3e # v1.0.0 - name: Run Cypress tests run: npx cypress run env: TEST_INBOX: ${{ steps.inbox.outputs.inbox }} CYPRESS_BASE_URL: ${{ secrets.STAGING_URL }} ``` ```javascript theme={null} // Use CI inbox or generate locally const inbox = Cypress.env('TEST_INBOX') ?? mail.generateInbox(); ``` # GitHub Actions Source: https://docs.zerodrop.dev/github-actions # GitHub Actions ZeroDrop has a native GitHub Action that generates a disposable inbox before your tests run and injects it as an environment variable. ## Basic usage ```yaml theme={null} name: E2E Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Generate test inbox id: inbox uses: zerodrop-dev/setup-zerodrop@36c6685cce921fbe8b6353a71292c8e262124e3e # v1.0.0 - name: Run Playwright tests run: npx playwright test env: TEST_INBOX: ${{ steps.inbox.outputs.inbox }} ``` ## Accessing the inbox in tests ```typescript theme={null} import { ZeroDrop } from 'zerodrop-client'; const mail = new ZeroDrop(); // Uses CI inbox if available, generates one locally otherwise const inbox = process.env.TEST_INBOX ?? mail.generateInbox(); ``` ## SHA pinning (recommended) Pin to a specific commit SHA for supply chain security: ```yaml theme={null} # Recommended — pin to SHA uses: zerodrop-dev/setup-zerodrop@36c6685cce921fbe8b6353a71292c8e262124e3e # v1.0.0 # Not recommended — floating tag uses: zerodrop-dev/setup-zerodrop@v1 ``` ## Parallel matrix builds Each matrix job gets its own isolated inbox automatically: ```yaml theme={null} jobs: test: strategy: matrix: browser: [chromium, firefox, webkit] steps: - name: Generate test inbox id: inbox uses: zerodrop-dev/setup-zerodrop@36c6685cce921fbe8b6353a71292c8e262124e3e - name: Run tests run: npx playwright test --project=${{ matrix.browser }} env: TEST_INBOX: ${{ steps.inbox.outputs.inbox }} ``` Each browser gets a different inbox — no collisions between parallel jobs. ## Full workflow example ```yaml theme={null} name: E2E Tests on: push: branches: [main] pull_request: jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Install Playwright browsers run: npx playwright install --with-deps chromium - name: Generate test inbox id: inbox uses: zerodrop-dev/setup-zerodrop@36c6685cce921fbe8b6353a71292c8e262124e3e # v1.0.0 - name: Run E2E tests run: npx playwright test env: TEST_INBOX: ${{ steps.inbox.outputs.inbox }} BASE_URL: ${{ secrets.STAGING_URL }} - name: Upload test results if: always() uses: actions/upload-artifact@v4 with: name: playwright-report path: playwright-report/ ``` ## Action outputs | Output | Description | Example | | ------- | ------------------ | ------------------------------------- | | `inbox` | Full email address | `swift-x7k2m@zerodrop-sandbox.online` | | `name` | Inbox name only | `swift-x7k2m` | ## Security The Action generates inbox names locally on the runner — no network request is made during generation. The inbox address is a random string; it does not contact ZeroDrop servers until your tests begin polling. See [SECURITY.md](https://github.com/zerodrop-dev/setup-zerodrop/blob/main/SECURITY.md) for full supply chain documentation. # Go SDK Source: https://docs.zerodrop.dev/go Test email flows in Go — OTPs and magic links auto-extracted. Zero dependencies, stdlib only. ## Install ```bash theme={null} go get github.com/zerodrop-dev/zerodrop-go ``` Zero dependencies — stdlib only. Go 1.21+. ## Quick start ```go theme={null} package main import ( "context" "fmt" "time" zerodrop "github.com/zerodrop-dev/zerodrop-go" ) func main() { client := zerodrop.New() inbox := client.GenerateInbox() // => "swift-x7k29a@zerodrop-sandbox.online" // Trigger your app's email flow with the inbox address... email, err := client.WaitForLatest(context.Background(), inbox, &zerodrop.Options{ Timeout: 15 * time.Second, }) if err != nil { panic(err) } fmt.Println(email.OTP) // "123456" — auto-extracted, no regex fmt.Println(email.MagicLink) // "https://..." — auto-extracted } ``` ## go test ```go theme={null} func TestSignupEmailVerification(t *testing.T) { client := zerodrop.New() inbox := client.GenerateInbox() // Trigger signup with the inbox address... email, err := client.WaitForLatest(context.Background(), inbox, &zerodrop.Options{ Timeout: 15 * time.Second, }) if err != nil { t.Fatalf("no verification email: %v", err) } if email.OTP == "" { t.Fatal("expected OTP in verification email") } } ``` ## SSE — sub-second delivery `WaitForLatest` uses Server-Sent Events by default with automatic polling fallback: ```go theme={null} // Force polling mode if needed email, err := client.WaitForLatest(ctx, inbox, &zerodrop.Options{ DisableSSE: true, }) ``` ## Filtering ```go theme={null} email, err := client.WaitForLatest(ctx, inbox, &zerodrop.Options{ Timeout: 15 * time.Second, Filter: &zerodrop.Filter{ From: "noreply@yourapp.com", Subject: "Verify", HasOTP: zerodrop.Bool(true), }, }) ``` All string filters are case-insensitive partial matches. ## Parallel tests `GenerateInbox` runs locally — safe with `t.Parallel()`, zero collisions: ```go theme={null} func TestParallelSignups(t *testing.T) { t.Parallel() client := zerodrop.New() inbox := client.GenerateInbox() // unique per call // ... } ``` ## Error handling ```go theme={null} var timeoutErr *zerodrop.TimeoutError if errors.As(err, &timeoutErr) { // No email arrived } if errors.Is(err, zerodrop.ErrUnauthorized) { // Invalid API key } ``` ## Workspaces & self-hosting ```go theme={null} client := zerodrop.New( zerodrop.WithAPIKey(os.Getenv("ZERODROP_API_KEY")), zerodrop.WithBaseURL("https://your-instance.yourdomain.com"), ) ``` ## Links * [pkg.go.dev reference](https://pkg.go.dev/github.com/zerodrop-dev/zerodrop-go) * [GitHub](https://github.com/zerodrop-dev/zerodrop-go) # Java SDK Source: https://docs.zerodrop.dev/java Test email flows in JUnit, Selenium and Playwright for Java — OTPs and magic links auto-extracted. ## Install **Gradle:** ```groovy theme={null} repositories { maven { url 'https://jitpack.io' } } dependencies { testImplementation 'com.github.zerodrop-dev:zerodrop-java:v0.1.0' } ``` **Maven:** ```xml theme={null} jitpack.io https://jitpack.io com.github.zerodrop-dev zerodrop-java v0.1.0 test ``` Java 11+ · one dependency (Gson). ## Quick start ```java theme={null} import dev.zerodrop.ZeroDrop; import dev.zerodrop.Email; import java.time.Duration; ZeroDrop mail = ZeroDrop.create(); String inbox = mail.generateInbox(); // => "swift-x7k29ab@zerodrop-sandbox.online" // Trigger your app's email flow with the inbox address... Email email = mail.waitForLatest(inbox, Duration.ofSeconds(15)); email.otp(); // Optional["123456"] — auto-extracted, no regex email.magicLink(); // Optional["https://..."] — auto-extracted ``` ## JUnit 5 + Selenium ```java theme={null} @Test void emailVerificationWithOtp() { ZeroDrop mail = ZeroDrop.create(); String inbox = mail.generateInbox(); driver.get("https://yourapp.com/signup"); driver.findElement(By.name("email")).sendKeys(inbox); driver.findElement(By.name("password")).sendKeys("TestPassword123!"); driver.findElement(By.cssSelector("button[type=submit]")).click(); Email email = mail.waitForLatest(inbox, Duration.ofSeconds(15)); String otp = email.otp().orElseThrow(); driver.findElement(By.name("otp")).sendKeys(otp); driver.findElement(By.cssSelector("button[type=submit]")).click(); assertTrue(driver.getCurrentUrl().endsWith("/dashboard")); } ``` ## Filtering ```java theme={null} import dev.zerodrop.Filter; Filter filter = Filter.builder() .from("noreply@yourapp.com") .hasOtp(true) .build(); Email email = mail.waitForLatest(inbox, Duration.ofSeconds(15), filter); ``` All string filters are case-insensitive partial matches. ## Parallel tests `generateInbox()` runs locally — safe with JUnit parallel execution and TestNG parallel suites. Every call returns a unique isolated inbox. ## Error handling ```java theme={null} try { Email email = mail.waitForLatest(inbox, Duration.ofSeconds(15)); } catch (ZeroDropException.Timeout e) { // No email arrived } catch (ZeroDropException.Auth e) { // Invalid API key } catch (ZeroDropException.Network e) { // Transport failure } ``` ## Workspaces & self-hosting ```java theme={null} ZeroDrop mail = ZeroDrop.builder() .apiKey(System.getenv("ZERODROP_API_KEY")) .baseUrl("https://your-instance.yourdomain.com") .build(); ``` ## Links * [JitPack](https://jitpack.io/#zerodrop-dev/zerodrop-java) * [GitHub](https://github.com/zerodrop-dev/zerodrop-java) # PHP SDK Source: https://docs.zerodrop.dev/php Test email flows in PHPUnit, Pest and Laravel Dusk — OTPs and magic links auto-extracted. ## Install ```bash theme={null} composer require --dev zerodrop/zerodrop ``` PHP 8.1+ · ext-curl · ext-json — no other dependencies. ## Quick start ```php theme={null} use ZeroDrop\Client; $mail = new Client(); $inbox = $mail->generateInbox(); // => "swift-x7k29ab@zerodrop-sandbox.online" // Trigger your app's email flow with the inbox address... $email = $mail->waitForLatest($inbox, timeout: 15); $email->otp; // "123456" — auto-extracted, no regex $email->magicLink; // "https://..." — auto-extracted ``` ## Laravel Dusk ```php theme={null} use Laravel\Dusk\Browser; use ZeroDrop\Client; public function testSignupEmailVerification(): void { $mail = new Client(); $inbox = $mail->generateInbox(); $this->browse(function (Browser $browser) use ($mail, $inbox) { $browser->visit('/signup') ->type('email', $inbox) ->type('password', 'TestPassword123!') ->press('Create account'); $email = $mail->waitForLatest($inbox, timeout: 15); $browser->type('otp', $email->otp) ->press('Verify') ->assertPathIs('/dashboard'); }); } ``` ## Pest ```php theme={null} use ZeroDrop\Client; it('verifies the account via emailed OTP', function () { $mail = new Client(); $inbox = $mail->generateInbox(); // Trigger signup with $inbox... $email = $mail->waitForLatest($inbox, timeout: 15); expect($email->otp)->not->toBeNull(); }); ``` ## Filtering ```php theme={null} use ZeroDrop\Filter; $email = $mail->waitForLatest( $inbox, timeout: 15, filter: new Filter( from: 'noreply@yourapp.com', hasOtp: true, ), ); ``` All string filters are case-insensitive partial matches. ## Parallel tests `generateInbox()` runs locally — safe with Paratest and parallel Dusk runs. Every call returns a unique isolated inbox. ## Error handling ```php theme={null} use ZeroDrop\Exception\TimeoutException; use ZeroDrop\Exception\AuthException; use ZeroDrop\Exception\NetworkException; try { $email = $mail->waitForLatest($inbox, timeout: 15); } catch (TimeoutException $e) { // No email arrived } catch (AuthException $e) { // Invalid API key } catch (NetworkException $e) { // Transport failure } ``` ## Workspaces & self-hosting ```php theme={null} $mail = new Client( apiKey: getenv('ZERODROP_API_KEY'), baseUrl: 'https://your-instance.yourdomain.com', ); ``` ## Links * [Packagist](https://packagist.org/packages/zerodrop/zerodrop) * [GitHub](https://github.com/zerodrop-dev/zerodrop-php) # Playwright Source: https://docs.zerodrop.dev/playwright # Playwright ZeroDrop integrates with Playwright for end-to-end email testing in CI pipelines. No Docker, no SMTP server, no mocking. ## Installation ```bash theme={null} npm install zerodrop-client npm install --save-dev @playwright/test ``` ## Basic setup ```typescript theme={null} import { test, expect } from '@playwright/test'; import { ZeroDrop } from 'zerodrop-client'; const mail = new ZeroDrop(); test('email verification flow', async ({ page }) => { // Generate a unique inbox for this test const inbox = mail.generateInbox(); // Use the inbox in your signup form await page.goto('/signup'); await page.fill('[data-testid="email"]', inbox); await page.click('[data-testid="submit"]'); // Wait for the verification email const email = await mail.waitForLatest(inbox, { timeout: 30000 }); // OTP and magic link are auto-extracted console.log(email.otp); // "123456" console.log(email.magicLink); // "https://yourapp.com/verify?token=..." // Click the verification link await page.goto(email.magicLink!); await expect(page).toHaveURL('/dashboard'); }); ``` ## OTP verification ZeroDrop extracts OTPs automatically at the edge — no regex needed in your tests. ```typescript theme={null} test('OTP login flow', async ({ page }) => { const inbox = mail.generateInbox(); await page.goto('/login'); await page.fill('[data-testid="email"]', inbox); await page.click('[data-testid="submit"]'); // Wait for OTP email const email = await mail.waitForLatest(inbox, { timeout: 30000 }); expect(email.otp).not.toBeNull(); // Enter OTP directly — no body parsing needed await page.fill('[data-testid="otp"]', email.otp!); await page.click('[data-testid="verify"]'); await expect(page).toHaveURL('/dashboard'); }); ``` ## Password reset flow ```typescript theme={null} test('password reset flow', async ({ page }) => { const inbox = mail.generateInbox(); // Request password reset await page.goto('/forgot-password'); await page.fill('[data-testid="email"]', inbox); await page.click('[data-testid="submit"]'); // Wait for reset email const email = await mail.waitForLatest(inbox, { timeout: 30000 }); // Navigate to reset link await page.goto(email.magicLink!); await page.fill('[data-testid="password"]', 'NewPassword123!'); await page.click('[data-testid="submit"]'); await expect(page).toHaveURL('/login'); }); ``` ## Parallel tests Every inbox is isolated — parallel tests never collide. ```typescript theme={null} test.describe.parallel('Email flows', () => { test('user A can verify email', async ({ page }) => { const inbox = mail.generateInbox(); // unique per test // ... }); test('user B can verify email', async ({ page }) => { const inbox = mail.generateInbox(); // different inbox // ... }); }); ``` ## Multiple emails in one test ```typescript theme={null} test('signup then password reset', async ({ page }) => { const inbox = mail.generateInbox(); let lastEmailId: string; // First email — verification const verifyEmail = await mail.waitForLatest(inbox, { timeout: 30000 }); lastEmailId = verifyEmail.id; await page.goto(verifyEmail.magicLink!); // Trigger password reset await page.goto('/forgot-password'); await page.fill('[data-testid="email"]', inbox); await page.click('[data-testid="submit"]'); // Poll until a NEW email arrives let resetEmail = null; const deadline = Date.now() + 30000; while (Date.now() < deadline) { const latest = await mail.fetchLatest(inbox); if (latest && latest.id !== lastEmailId) { resetEmail = latest; break; } await new Promise(r => setTimeout(r, 2000)); } expect(resetEmail).not.toBeNull(); await page.goto(resetEmail!.magicLink!); }); ``` ## Timeout options ```typescript theme={null} // Default: 10 seconds const email = await mail.waitForLatest(inbox); // Custom timeout const email = await mail.waitForLatest(inbox, { timeout: 30000 }); // Force polling mode (disable SSE) const email = await mail.waitForLatest(inbox, { sse: false }); ``` ## playwright.config.ts ```typescript theme={null} import { defineConfig } from '@playwright/test'; export default defineConfig({ timeout: 60000, // allow time for email delivery use: { baseURL: 'http://localhost:3000', }, }); ``` # Python SDK Source: https://docs.zerodrop.dev/python ZeroDrop Python SDK reference — email.otp and email.magic_link auto-extracted. No Docker, no regex, no signup. # Python SDK Full API reference for `zerodrop` — the ZeroDrop Python SDK. ## Installation ```bash theme={null} pip install zerodrop ``` No dependencies. Python 3.8+. ## ZeroDrop class ```python theme={null} from zerodrop import ZeroDrop mail = ZeroDrop(api_key=None, base_url="https://zerodrop.dev") ``` ### Parameters | Parameter | Type | Description | | ---------- | ---------------- | ----------------------------------------- | | `api_key` | `str` (optional) | API key for Workspaces tier | | `base_url` | `str` (optional) | Custom base URL for self-hosted instances | Without an API key, ZeroDrop runs in free sandbox mode with a shared domain and 30-minute TTL. *** ## generate\_inbox() ```python theme={null} mail.generate_inbox() -> str ``` Generates a unique inbox address instantly. No network request. ```python theme={null} inbox = mail.generate_inbox() # "swift-x7k2m@zerodrop-sandbox.online" ``` *** ## wait\_for\_latest() ```python theme={null} mail.wait_for_latest( inbox: str, timeout: int = 10000, poll_interval: float = 2.0, filter_: Optional[ZeroDropFilter] = None ) -> ZeroDropEmail ``` Polls until an email arrives in the inbox. Raises `ZeroDropTimeoutError` if no email arrives within the timeout. ### Parameters | Parameter | Type | Default | Description | | --------------- | ---------------- | -------- | ---------------------------------------- | | `inbox` | `str` | required | Inbox address to poll | | `timeout` | `int` | `10000` | Milliseconds to wait | | `poll_interval` | `float` | `2.0` | Seconds between polls | | `filter_` | `ZeroDropFilter` | `None` | Optional filter to match specific emails | ```python theme={null} # Default — 10s timeout email = mail.wait_for_latest(inbox) # Custom timeout email = mail.wait_for_latest(inbox, timeout=30000) # With filter email = mail.wait_for_latest(inbox, filter_=ZeroDropFilter(has_otp=True)) ``` *** ## fetch\_latest() ```python theme={null} mail.fetch_latest( inbox: str, filter_: Optional[ZeroDropFilter] = None ) -> Optional[ZeroDropEmail] ``` Fetches the latest email without waiting. Returns `None` if the inbox is empty or no email matches the filter. ```python theme={null} email = mail.fetch_latest(inbox) if email: print(email.subject) ``` *** ## Email Filtering Filter emails by sender, subject, body, or extracted fields. Useful when multiple emails land in the same inbox. ```python theme={null} from zerodrop import ZeroDrop, ZeroDropFilter mail = ZeroDrop() # Only match emails from a specific sender email = mail.wait_for_latest(inbox, filter_=ZeroDropFilter( from_="noreply@yourapp.com" )) # Only match emails with a specific subject email = mail.wait_for_latest(inbox, filter_=ZeroDropFilter( subject="Verify your email" )) # Only match emails that contain an OTP email = mail.wait_for_latest(inbox, filter_=ZeroDropFilter( has_otp=True )) # Only match emails that contain a magic link email = mail.wait_for_latest(inbox, filter_=ZeroDropFilter( has_magic_link=True )) # Combine multiple filters email = mail.wait_for_latest(inbox, timeout=15000, filter_=ZeroDropFilter( from_="noreply@yourapp.com", subject="Reset", has_magic_link=True )) ``` All string filters are case-insensitive partial matches. *** ## ZeroDropEmail type ```python theme={null} @dataclass class ZeroDropEmail: id: str # Unique message ID from_: str # Sender address to: str # Recipient address subject: str # Email subject body: str # Parsed plain-text body raw_body: str # Full raw MIME message received_at: datetime # UTC timestamp otp: Optional[str] # Auto-extracted OTP code (4-8 digits) magic_link: Optional[str] # Auto-extracted verification/reset link ``` ### otp 4-8 digit numeric code extracted from the email body. Detected near labels like `code`, `otp`, `pin`, `verification`. `None` if not detected. ```python theme={null} email = mail.wait_for_latest(inbox) print(email.otp) # "123456" or None ``` ### magic\_link Verification or reset URL extracted from the email body. Detected for URLs containing `verify`, `confirm`, `reset`, `token`, `activate`, or `auth`. `None` if not detected. ```python theme={null} print(email.magic_link) # "https://app.com/verify?token=abc" or None ``` *** ## ZeroDropFilter type ```python theme={null} @dataclass class ZeroDropFilter: from_: Optional[str] = None # Partial match on sender address subject: Optional[str] = None # Partial match on subject line body: Optional[str] = None # Partial match on email body has_otp: Optional[bool] = None # Only match emails with an OTP has_magic_link: Optional[bool] = None # Only match emails with a magic link ``` *** ## Error types ### ZeroDropTimeoutError Raised when no email arrives within the timeout. ```python theme={null} from zerodrop import ZeroDrop, ZeroDropTimeoutError try: email = mail.wait_for_latest(inbox, timeout=10000) except ZeroDropTimeoutError: print("No email received in time") ``` ### ZeroDropNetworkError Raised when the API is unreachable. Includes a link to the status page. ```python theme={null} from zerodrop import ZeroDropNetworkError try: email = mail.fetch_latest(inbox) except ZeroDropNetworkError as e: print(f"Network error — check https://zerodrop.instatus.com") ``` ### ZeroDropAuthError Raised when an invalid API key is provided. *** ## pytest example ```python theme={null} import pytest from zerodrop import ZeroDrop mail = ZeroDrop() def test_signup_email_verification(page): inbox = mail.generate_inbox() page.goto("/signup") page.fill('[name="email"]', inbox) page.fill('[name="password"]', "TestPassword123!") page.click('[type="submit"]') # Wait for verification email email = mail.wait_for_latest(inbox, timeout=15000) # OTP auto-extracted — no regex needed assert email.otp is not None page.fill('[name="otp"]', email.otp) page.click('[type="submit"]') assert page.url == "/dashboard" def test_magic_link_login(page): inbox = mail.generate_inbox() page.goto("/login") page.fill('[name="email"]', inbox) page.click('button:has-text("Send magic link")') email = mail.wait_for_latest(inbox, timeout=15000) # magic_link auto-extracted — no HTML parsing needed assert email.magic_link is not None page.goto(email.magic_link) assert page.url == "/dashboard" ``` *** ## Parallel test runs `generate_inbox()` runs locally — no network request, no throttling: ```python theme={null} # Safe to run in parallel — generate_inbox() is local inboxes = [mail.generate_inbox() for _ in range(50)] ``` *** ## Free vs Workspace | | Free | Workspace | | --------------------- | ------ | --------- | | Inbox generation | ✓ | ✓ | | OTP auto-extraction | ✓ | ✓ | | Magic link extraction | ✓ | ✓ | | Email filtering | ✓ | ✓ | | Email retention | 30 min | Extended | | Custom domains | ✗ | ✓ | | API key | ✗ | ✓ | | Webhooks | ✗ | ✓ | | AI spam filter | On | Off | Get a Workspace at [zerodrop.dev/pricing](https://zerodrop.dev/pricing) *** ## Zero telemetry The SDK does not send analytics, usage metrics, or environment data to any server. The only network requests made are explicit inbox polls to `zerodrop.dev/api/inbox/{name}`. Your CI pipeline is your business. # Quickstart Source: https://docs.zerodrop.dev/quickstart # Quick Start ZeroDrop gives you a disposable email inbox in one line of code. No signup, no Docker, no SMTP config. ## Install the SDK ```bash theme={null} npm install zerodrop-client ``` ## Generate an inbox ```typescript theme={null} import { ZeroDrop } from 'zerodrop-client'; const mail = new ZeroDrop(); const inbox = mail.generateInbox(); console.log(inbox); // swift-x7k2m@zerodrop-sandbox.online ``` The inbox is ready instantly — no network request needed to generate it. ## Wait for an email ```typescript theme={null} const email = await mail.waitForLatest(inbox, { timeout: 30000 }); console.log(email.subject); // "Verify your email" console.log(email.otp); // "123456" — auto-extracted console.log(email.magicLink); // "https://..." — auto-extracted ``` `waitForLatest` uses SSE for sub-second delivery. Falls back to polling automatically if SSE is unavailable. ## Full example ```typescript theme={null} import { test, expect } from '@playwright/test'; import { ZeroDrop } from 'zerodrop-client'; const mail = new ZeroDrop(); test('user can sign up and verify email', async ({ page }) => { const inbox = mail.generateInbox(); await page.goto('/signup'); await page.fill('[data-testid="email"]', inbox); await page.click('[data-testid="submit"]'); const email = await mail.waitForLatest(inbox, { timeout: 30000 }); // OTP already extracted — no regex needed await page.fill('[data-testid="otp"]', email.otp!); await page.click('[data-testid="verify"]'); await expect(page).toHaveURL('/dashboard'); }); ``` ## Next steps * [Playwright guide](/playwright) — full email verification test examples * [GitHub Actions](/github-actions) — use ZeroDrop in CI pipelines * [SDK reference](/sdk-reference) — full API documentation * [Self-hosting](/self-hosting) — deploy your own instance # Ruby SDK Source: https://docs.zerodrop.dev/ruby Test email flows in RSpec, Minitest and Capybara — OTPs and magic links auto-extracted. Zero dependencies. ## Install ```bash theme={null} gem install zerodrop ``` Or in your Gemfile: ```ruby theme={null} gem "zerodrop", group: :test ``` Zero runtime dependencies. Ruby 3.0+. ## Quick start ```ruby theme={null} require "zerodrop" mail = ZeroDrop::Client.new inbox = mail.generate_inbox # => "swift-x7k29ab@zerodrop-sandbox.online" # Trigger your app's email flow with the inbox address... email = mail.wait_for_latest(inbox, timeout: 15) email.otp # => "123456" — auto-extracted, no regex email.magic_link # => "https://..." — auto-extracted ``` ## RSpec + Capybara ```ruby theme={null} RSpec.describe "Signup email verification", type: :system do let(:mail) { ZeroDrop::Client.new } it "verifies the account via emailed OTP" do inbox = mail.generate_inbox visit "/signup" fill_in "Email", with: inbox fill_in "Password", with: "TestPassword123!" click_button "Create account" email = mail.wait_for_latest(inbox, timeout: 15) expect(email.otp).not_to be_nil fill_in "Code", with: email.otp click_button "Verify" expect(page).to have_current_path("/dashboard") end end ``` ## Filtering ```ruby theme={null} email = mail.wait_for_latest( inbox, timeout: 15, filter: ZeroDrop::Filter.new( from: "noreply@yourapp.com", has_otp: true ) ) ``` All string filters are case-insensitive partial matches. ## Parallel tests `generate_inbox` runs locally — safe with parallel\_tests, flatware, turbo\_tests. Every call returns a unique isolated inbox. ## Error handling ```ruby theme={null} begin email = mail.wait_for_latest(inbox, timeout: 15) rescue ZeroDrop::TimeoutError # No email arrived rescue ZeroDrop::AuthError # Invalid API key rescue ZeroDrop::NetworkError => e # Transport failure end ``` ## Workspaces & self-hosting ```ruby theme={null} mail = ZeroDrop::Client.new( api_key: ENV["ZERODROP_API_KEY"], base_url: "https://your-instance.yourdomain.com" ) ``` ## Links * [RubyGems](https://rubygems.org/gems/zerodrop) * [GitHub](https://github.com/zerodrop-dev/zerodrop-ruby) # SDK Reference Source: https://docs.zerodrop.dev/sdk-reference # SDK Reference Full API reference for `zerodrop-client`. ## Installation ```bash theme={null} npm install zerodrop-client ``` ## ZeroDrop class ```typescript theme={null} import { ZeroDrop } from 'zerodrop-client'; const mail = new ZeroDrop(apiKey?, options?); ``` ### Parameters | Parameter | Type | Description | | ----------------- | ------------------- | ----------------------------------------- | | `apiKey` | `string` (optional) | API key for Workspaces tier | | `options.baseUrl` | `string` (optional) | Custom base URL for self-hosted instances | Without an API key, ZeroDrop runs in free sandbox mode with a shared domain and 30-minute TTL. *** ## generateInbox() ```typescript theme={null} mail.generateInbox(): string ``` Generates a unique inbox address instantly. No network request. ```typescript theme={null} const inbox = mail.generateInbox(); // "swift-x7k2m@zerodrop-sandbox.online" ``` *** ## waitForLatest() ```typescript theme={null} mail.waitForLatest(inbox: string, options?): Promise ``` Waits for an email to arrive. Uses SSE for sub-second delivery, falls back to polling automatically. Throws `ZeroDropTimeoutError` if no email arrives within the timeout. ### Options | Option | Type | Default | Description | | -------------- | --------- | ------- | ------------------------------------------ | | `timeout` | `number` | `10000` | Milliseconds to wait | | `pollInterval` | `number` | `2000` | Milliseconds between polls (fallback mode) | | `sse` | `boolean` | `true` | Use SSE for delivery | ```typescript theme={null} // Default — SSE, 10s timeout const email = await mail.waitForLatest(inbox); // Custom timeout const email = await mail.waitForLatest(inbox, { timeout: 30000 }); // Force polling const email = await mail.waitForLatest(inbox, { sse: false }); ``` *** ## fetchLatest() ```typescript theme={null} mail.fetchLatest(inbox: string): Promise ``` Fetches the latest email without waiting. Returns `null` if the inbox is empty. ```typescript theme={null} const email = await mail.fetchLatest(inbox); if (email) { console.log(email.subject); } ``` *** ## ZeroDropEmail type ```typescript theme={null} interface ZeroDropEmail { id: string; // Unique message ID from: string; // Sender address to: string; // Recipient address subject: string; // Email subject body: string; // Parsed plain-text body rawBody: string; // Full raw MIME message receivedAt: Date; // UTC timestamp otp: string | null; // Auto-extracted OTP code (4-8 digits) magicLink: string | null; // Auto-extracted verification/reset link } ``` ### otp 4-8 digit numeric code extracted from the email body. Detected near labels like `code`, `otp`, `pin`, `verification`. `null` if not detected. ```typescript theme={null} const email = await mail.waitForLatest(inbox); console.log(email.otp); // "123456" or null ``` ### magicLink Verification or reset URL extracted from the email body. Detected for URLs containing `verify`, `confirm`, `reset`, `token`, `activate`, or `auth`. `null` if not detected. ```typescript theme={null} console.log(email.magicLink); // "https://app.com/verify?token=abc" or null ``` *** ## Error types ### ZeroDropTimeoutError Thrown when no email arrives within the timeout. ```typescript theme={null} import { ZeroDropTimeoutError } from 'zerodrop-client'; try { const email = await mail.waitForLatest(inbox, { timeout: 10000 }); } catch (err) { if (err instanceof ZeroDropTimeoutError) { console.log('No email received in time'); } } ``` ### ZeroDropNetworkError Thrown when the API is unreachable. Includes a link to the status page. ```typescript theme={null} import { ZeroDropNetworkError } from 'zerodrop-client'; try { const email = await mail.fetchLatest(inbox); } catch (err) { if (err instanceof ZeroDropNetworkError) { console.log('Network error — check https://zerodrop.instatus.com'); } } ``` ### ZeroDropAuthError Thrown when an invalid API key is provided. *** ## Zero telemetry The SDK does not send analytics, usage metrics, or environment data to any server. The only network requests made are explicit inbox polls to `zerodrop.dev/api/inbox/{name}`. Your CI pipeline is your business. # Self-Hosting Source: https://docs.zerodrop.dev/self-hosting # Self-Hosting ZeroDrop's email worker is fully open source. Teams with strict compliance requirements can deploy their own instance. ## What you need * A Cloudflare account with Workers enabled * A domain added to Cloudflare (for Email Routing) * An Upstash Redis database * Node.js 18+ and Wrangler CLI ## Setup ```bash theme={null} git clone https://github.com/zerodrop-dev/zerodrop-worker.git cd zerodrop-worker npm install ``` Set your secrets: ```bash theme={null} npx wrangler secret put UPSTASH_REDIS_REST_URL npx wrangler secret put UPSTASH_REDIS_REST_TOKEN ``` Deploy: ```bash theme={null} npx wrangler deploy ``` Configure Cloudflare Email Routing to send all emails at `*@yourdomain.com` to your deployed worker. ## Point the SDK at your instance ```typescript theme={null} const mail = new ZeroDrop(undefined, { baseUrl: 'https://your-dashboard.yourdomain.com' }); ``` ## Full guide See [SELF\_HOSTING.md](https://github.com/zerodrop-dev/zerodrop-worker/blob/master/SELF_HOSTING.md) in the worker repo for the complete setup guide including wrangler.toml configuration, disabling the AI spam filter, and cost estimates. ## Cost estimate * **Cloudflare Workers** — free tier: 100,000 requests/day * **Cloudflare Email Routing** — free * **Cloudflare Workers AI** — free tier: 10,000 neurons/day * **Upstash Redis** — free tier: 10,000 commands/day For most CI pipelines, self-hosting runs entirely within free tiers.