PlopPlop Docs

Quick Start

Get started testing emails with plop.email in 5 minutes.

Add email testing to your application in minutes. No mail servers to configure—just API calls.

1. Get Your API Key

Sign up at plop.email and create an API key from your dashboard.

2. Choose Your Integration

import { test, expect } from '@playwright/test';

test('signup sends welcome email', async ({ page, request }) => {
  const testEmail = `signup+${Date.now()}@in.plop.email`;

  // Trigger signup
  await page.goto('/signup');
  await page.fill('[name="email"]', testEmail);
  await page.fill('[name="password"]', 'SecurePass123!');
  await page.click('button[type="submit"]');

  // Wait and fetch email
  await page.waitForTimeout(2000);
  const response = await request.get(
    'https://api.plop.email/v1/messages/latest',
    {
      params: { to: testEmail },
      headers: { Authorization: `Bearer ${process.env.PLOP_API_KEY}` },
    }
  );
  const email = await response.json();

  // Assert
  expect(email.data.subject).toContain('Welcome');
  expect(email.data.htmlContent).toContain('Get Started');
});
// cypress/support/commands.ts
Cypress.Commands.add('getLatestEmail', (to: string) => {
  return cy.request({
    method: 'GET',
    url: 'https://api.plop.email/v1/messages/latest',
    headers: { Authorization: `Bearer ${Cypress.env('PLOP_API_KEY')}` },
    qs: { to },
  }).its('body.data');
});

// cypress/e2e/signup.cy.ts
it('sends welcome email', () => {
  const testEmail = `signup+${Date.now()}@in.plop.email`;

  cy.visit('/signup');
  cy.get('[name="email"]').type(testEmail);
  cy.get('[name="password"]').type('SecurePass123!');
  cy.get('form').submit();

  cy.wait(2000);
  cy.getLatestEmail(testEmail).then((email) => {
    expect(email.subject).to.include('Welcome');
  });
});
import { sendWelcomeEmail } from '../src/email';

async function fetchEmail(to: string) {
  const response = await fetch(
    `https://api.plop.email/v1/messages/latest?to=${encodeURIComponent(to)}`,
    { headers: { Authorization: `Bearer ${process.env.PLOP_API_KEY}` } }
  );
  return (await response.json()).data;
}

test('sends welcome email', async () => {
  const testEmail = `welcome+${Date.now()}@in.plop.email`;

  await sendWelcomeEmail({ to: testEmail, name: 'Test User' });
  await new Promise(r => setTimeout(r, 2000));

  const email = await fetchEmail(testEmail);

  expect(email.subject).toBe('Welcome to Our App!');
  expect(email.htmlContent).toContain('Test User');
});
import pytest
import requests
import time
import os

@pytest.fixture
def plop_client():
    class PlopClient:
        def get_latest(self, to: str):
            response = requests.get(
                "https://api.plop.email/v1/messages/latest",
                params={"to": to},
                headers={"Authorization": f"Bearer {os.environ['PLOP_API_KEY']}"},
            )
            return response.json()["data"]
    return PlopClient()

def test_welcome_email(plop_client):
    test_email = f"pytest+{int(time.time())}@in.plop.email"

    # Send email from your app
    send_welcome_email(to=test_email, name="Test User")
    time.sleep(2)

    # Verify
    email = plop_client.get_latest(test_email)
    assert "Welcome" in email["subject"]
    assert "Test User" in email["htmlContent"]
# Fetch latest email for a specific address
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.plop.email/v1/messages/latest?to=test@in.plop.email"

# Response
{
  "data": {
    "id": "msg_abc123",
    "subject": "Welcome!",
    "from": "hello@yourapp.com",
    "to": "test@in.plop.email",
    "htmlContent": "<html>...</html>",
    "textContent": "Welcome to our app!",
    "receivedAt": "2025-01-01T12:00:00.000Z"
  }
}

Using an SDK? The official TypeScript SDK and Python SDK handle polling and error handling for you — no manual fetch or waitForTimeout needed.

3. Key Concepts

Unique Test Addresses

Generate a unique email for each test to avoid conflicts:

const testEmail = `signup+${Date.now()}@in.plop.email`;
// Result: signup+1704067200000@in.plop.email

The +tag part (plus-addressing) isolates each test run while using the same mailbox.

Polling for Email Arrival

Emails may take 1-3 seconds to arrive. Use polling with retry logic:

async function waitForEmail(to: string, timeout = 10000) {
  const start = Date.now();
  while (Date.now() - start < timeout) {
    const response = await fetch(
      `https://api.plop.email/v1/messages/latest?to=${to}`,
      { headers: { Authorization: `Bearer ${process.env.PLOP_API_KEY}` } }
    );
    if (response.ok) {
      const { data } = await response.json();
      if (data) return data;
    }
    await new Promise(r => setTimeout(r, 1000));
  }
  throw new Error(`No email received for ${to}`);
}

Tip: The official SDKs handle polling automatically with plop.messages.waitFor() (TypeScript) or plop.messages.wait_for() (Python). See TypeScript SDK or Python SDK.

Common patterns for extracting verification links and OTPs:

// Extract link from HTML
const linkMatch = email.htmlContent.match(/href="([^"]*verify[^"]*)"/);
const verifyLink = linkMatch?.[1];

// Extract 6-digit OTP from text
const otpMatch = email.textContent.match(/\b(\d{6})\b/);
const otp = otpMatch?.[1];

Environment Setup

CI/CD (GitHub Actions)

- name: Run E2E tests
  run: npm run test:e2e
  env:
    PLOP_API_KEY: ${{ secrets.PLOP_API_KEY }}

Local Development

# .env
PLOP_API_KEY=your_api_key_here

Response Fields

The API returns these fields for each message:

FieldTypeDescription
idstringUnique message ID
subjectstringEmail subject line
fromstringSender address
tostringRecipient address
htmlContentstringHTML body content
textContentstringPlain text content
receivedAtstringISO timestamp
headersobjectEmail headers

Next Steps

On this page