Menu

Automated Testing

Set up the Mailloop SDK for programmatic email testing in CI/CD pipelines.

5 min read

Mailloop provides a TypeScript SDK that lets you create sandboxes, send emails through them, and verify delivery programmatically. This is designed for automated test suites and CI/CD pipelines.

Getting an API Key

Go to your dashboard, open Settings > API Keys, and create a new key. Keys use the format ml_live_.... Store it as an environment variable:

BASH
export MAILLOOP_API_KEY=ml_live_your_api_key_here

SDK Installation

BASH
# npm
npm install @mailloop/sdk

# yarn
yarn add @mailloop/sdk

# pnpm
pnpm add @mailloop/sdk

Requires Node.js 18 or later. Zero dependencies.

Basic Pattern

The typical automated email test follows this flow:

  1. Create a temporary sandbox
  2. Configure your application to send through it
  3. Trigger the action that sends email
  4. Wait for the email to arrive
  5. Assert on the email content

TYPESCRIPT
import { MailloopClient } from '@mailloop/sdk';

const client = new MailloopClient({
  apiKey: process.env.MAILLOOP_API_KEY
});

// 1. Create a temporary sandbox (auto-deletes after 60 seconds)
const sandbox = await client.sandboxes.createTemporary({
  duration: 60
});

// 2-3. Your application sends email using sandbox.username / sandbox.password
//      as SMTP credentials against sandbox.mailloop.io

// 4. Wait for the email
const email = await client.emails.waitFor(sandbox.id, {
  subject: 'Welcome',
  timeout: 30000
});

// 5. Assert
console.log(email.subject);  // "Welcome to Acme"
console.log(email.html);     // Full HTML body
console.log(email.to);       // ["[email protected]"]

Temporary Sandboxes

Temporary sandboxes auto-delete after a specified duration (1 to 60 seconds). They are ideal for test isolation because each test gets a clean, independent inbox that cleans itself up.

TYPESCRIPT
const sandbox = await client.sandboxes.createTemporary({
  duration: 30,
  name: 'signup-test'
});

// sandbox.isTemporary === true
// sandbox.deleteAfter === ISO timestamp

For longer-running test suites, use persistent sandboxes and delete them manually:

TYPESCRIPT
const sandbox = await client.sandboxes.create({
  name: 'e2e-tests'
});

// ... run tests ...

await client.sandboxes.delete(sandbox.id);

Waiting for Emails

The waitFor method polls server-side and returns when a matching email arrives. No client-side polling loop needed.

TYPESCRIPT
// Wait for any email
const email = await client.emails.waitFor(sandbox.id);

// Wait for email from a specific sender
const email = await client.emails.waitFor(sandbox.id, {
  from: '[email protected]'
});

// Wait for email with a subject substring match
const email = await client.emails.waitFor(sandbox.id, {
  subject: 'Password Reset'
});

// Combine filters
const email = await client.emails.waitFor(sandbox.id, {
  from: '[email protected]',
  subject: 'Invoice',
  timeout: 45000
});

The maximum timeout is 60 seconds (default is 30 seconds). If no matching email arrives, a TimeoutError is thrown.

Test Framework Examples

Vitest / Jest

TYPESCRIPT
import { describe, it, expect } from 'vitest';
import { MailloopClient } from '@mailloop/sdk';

const client = new MailloopClient({
  apiKey: process.env.MAILLOOP_API_KEY
});

describe('signup emails', () => {
  it('sends a welcome email after registration', async () => {
    const sandbox = await client.sandboxes.createTemporary({
      duration: 60
    });

    // Trigger your app to send email using sandbox SMTP credentials
    await registerUser({
      email: '[email protected]',
      smtpHost: 'sandbox.mailloop.io',
      smtpPort: 587,
      smtpUser: sandbox.username,
      smtpPass: sandbox.password
    });

    const email = await client.emails.waitFor(sandbox.id, {
      subject: 'Welcome',
      timeout: 15000
    });

    expect(email.subject).toContain('Welcome');
    expect(email.to).toContain('[email protected]');
    expect(email.html).toContain('Confirm your email');
  });
});

Playwright (E2E)

TYPESCRIPT
import { test, expect } from '@playwright/test';
import { MailloopClient } from '@mailloop/sdk';

const client = new MailloopClient({
  apiKey: process.env.MAILLOOP_API_KEY
});

test('password reset flow sends email with valid link', async ({ page }) => {
  const sandbox = await client.sandboxes.createTemporary({
    duration: 60
  });

  // Navigate to password reset page
  await page.goto('/forgot-password');
  await page.fill('[name="email"]', sandbox.emailAddress);
  await page.click('button[type="submit"]');

  // Wait for the reset email
  const email = await client.emails.waitFor(sandbox.id, {
    subject: 'Reset your password',
    timeout: 30000
  });

  // Extract and follow the reset link
  const resetLink = email.html.match(/href="([^"]*reset[^"]*)"/)?.[1];
  expect(resetLink).toBeTruthy();

  await page.goto(resetLink);
  await expect(page.locator('h1')).toContainText('New Password');
});

CI/CD Integration

GitHub Actions

YAML
name: Email Tests
on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - run: npm ci
      - run: npm test
        env:
          MAILLOOP_API_KEY: ${{ secrets.MAILLOOP_API_KEY }}

Store MAILLOOP_API_KEY as a repository secret in GitHub. The SDK reads it from process.env.MAILLOOP_API_KEY in your test setup.

Environment Variables

For any CI provider, set these environment variables:

VariableValueDescription
MAILLOOP_API_KEYml_live_...Your API key

The SDK only needs the API key. SMTP credentials are generated per-sandbox at runtime.

Tips

Parallel Tests

Each test can create its own temporary sandbox, so tests run in parallel without interfering with each other:

TYPESCRIPT
// test-a.spec.ts -- gets its own sandbox
const sandbox = await client.sandboxes.createTemporary({ duration: 60 });

// test-b.spec.ts -- gets a different sandbox
const sandbox = await client.sandboxes.createTemporary({ duration: 60 });

Listing Emails

If you need to check multiple emails instead of waiting for a specific one:

TYPESCRIPT
const { emails, total } = await client.emails.list(sandbox.id, {
  limit: 10
});

for (const email of emails) {
  console.log(`${email.from}: ${email.subject}`);
}

Inspecting Full Email Details

The waitFor and get methods return the full email including HTML, text, headers, and attachment metadata:

TYPESCRIPT
const email = await client.emails.get(sandbox.id, emailId);

console.log(email.html);         // Full HTML body
console.log(email.text);         // Plain text body
console.log(email.headers);      // All email headers
console.log(email.attachments);  // Attachment metadata (filename, size, type)

See the SDK Reference for the complete API.