Menu

SDK Reference

Complete reference for the @mailloop/sdk TypeScript SDK.

8 min read

The official Mailloop SDK for JavaScript and TypeScript. Zero dependencies, full type safety, and dual ESM/CommonJS builds.

Installation

BASH
# npm
npm install @mailloop/sdk

# yarn
yarn add @mailloop/sdk

# pnpm
pnpm add @mailloop/sdk

Requirements: Node.js 18 or later (uses native fetch).

MailloopClient

The main entry point for the SDK.

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

const client = new MailloopClient({
  apiKey: 'ml_live_your_api_key_here'
});

Options

OptionTypeRequiredDefaultDescription
apiKeystringYes--API key from the dashboard
baseUrlstringNohttps://api.mailloop.io/v1API base URL
timeoutnumberNo30000Request timeout in milliseconds

Sandboxes

Manage email testing sandboxes.

client.sandboxes.create(params)

Create a new persistent sandbox.

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

Parameters:

ParamTypeRequiredDescription
namestringYesSandbox name (1–100 characters)

Returns: Sandbox

client.sandboxes.createTemporary(params)

Create a sandbox that auto-deletes after the specified duration. Ideal for CI/CD and automated tests.

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

Parameters:

ParamTypeRequiredDescription
durationnumberYesSeconds until auto-deletion (1–86400, up to 24 hours)
namestringNoOptional sandbox name

Returns: Sandbox with isTemporary: true and deleteAfter set.

client.sandboxes.list()

List all sandboxes for your organization.

TYPESCRIPT
const { sandboxes } = await client.sandboxes.list();

Returns: { sandboxes: Sandbox[] }

client.sandboxes.get(id)

Get a sandbox by ID.

TYPESCRIPT
const sandbox = await client.sandboxes.get('sandbox-id');

Returns: Sandbox

client.sandboxes.update(id, params)

Update a sandbox.

TYPESCRIPT
const updated = await client.sandboxes.update('sandbox-id', {
  name: 'renamed-sandbox'
});

Parameters:

ParamTypeRequiredDescription
namestringNoNew sandbox name

Returns: Sandbox

client.sandboxes.delete(id)

Permanently delete a sandbox and all its emails.

TYPESCRIPT
await client.sandboxes.delete('sandbox-id');

Returns: void


Emails

List, retrieve, and wait for emails in a sandbox.

client.emails.list(sandboxId, params?)

List emails in a sandbox, ordered by received time (newest first).

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

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

Parameters:

ParamTypeRequiredDefaultDescription
limitnumberNo20Max emails to return (1–100)
offsetnumberNo0Number of emails to skip
afterstringNo--Cursor: an email ID you already saw. Returns emails older than it, since the list is newest first. Takes precedence over offset.

Returns: { emails: EmailSummary[], total: number, nextCursor: string | null, hasMore: boolean }

total is the sandbox-wide count on every page, including cursor pages. Page to the end by passing nextCursor back as after until hasMore is false.

client.emails.get(sandboxId, emailId)

Get the full details of a specific email, including HTML body, headers, and attachments.

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

console.log(email.subject);
console.log(email.html);
console.log(email.headers);
console.log(email.attachments);

Returns: EmailDetail

client.emails.waitFor(sandboxId, params?)

Wait for an email matching the given filters. The server watches the sandbox and returns as soon as a match is found, including one that already landed in the last 15 seconds. If the send happened earlier in the run, widen that lookback with sinceMs.

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

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

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

Parameters:

ParamTypeRequiredDefaultDescription
timeoutnumberNo30000Max wait time in ms. Must be between 1000 and 60000; anything else throws ValidationError and the API rejects it too, it is never clamped.
sinceMsnumberNo15000How far back to look for an email that already arrived, in ms. Max 900000 (15 minutes). Pass 0 to match only mail arriving during the call, and keep it tight in a sandbox you reuse across cases.
fromstringNo--Filter by sender address
tostringNo--Filter by recipient address
subjectstringNo--Filter by subject

Filters are case-insensitive literal substrings, never patterns, so %, _, ., *, +, [, ], (, ), $ and ^ match themselves. All supplied filters must match. Both sides are Unicode-normalised and whitespace-collapsed before comparing, so a subject copied straight out of a listing works as-is.

Returns: EmailDetail

Throws: TimeoutError if no matching email arrives within the timeout.

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

try {
  const email = await client.emails.waitFor('sandbox-id', {
    subject: 'Welcome',
    timeout: 10000
  });
} catch (error) {
  if (error instanceof TimeoutError) {
    console.log('No matching email arrived within 10 seconds');
  }
}

client.emails.send(params) (Experimental, not generally available)

Note: Outbound sending is experimental. The endpoint requires the

emails:send permission, which cannot be selected when creating an API key,

so a call returns a forbidden error unless Mailloop granted it to your key.

Mailloop captures mail; it does not send on your behalf. Documented here only

so the method in your editor's autocomplete is explained.

Send a transactional email with block-based content.

TYPESCRIPT
const result = await client.emails.send({
  from: '[email protected]',
  to: ['[email protected]'],
  subject: 'Your order has shipped',
  blocks: [
    { type: 'heading', content: 'Order Shipped', level: 2 },
    { type: 'paragraph', content: 'Your order #4821 is on its way.' },
    { type: 'button', text: 'Track Package', href: 'https://acme.com/track/4821' }
  ],
  layout: {
    primaryColor: '#4F46E5',
    companyName: 'Acme Inc.'
  }
});

console.log(result.id);     // Email ID
console.log(result.status); // "sent" or "failed"

Block types: paragraph, heading, button, image, spacer, divider, code, list, callout.


Types

Sandbox

TYPESCRIPT
interface Sandbox {
  id: string;
  name: string;
  username: string;
  password: string;
  emailAddress: string;   // [email protected]
  emailCount: number;
  isTemporary: boolean;
  deleteAfter: string | null; // ISO timestamp
  createdAt: string;          // ISO timestamp
  updatedAt: string;          // ISO timestamp
}

EmailSummary

Returned by emails.list(). Contains metadata without the full body.

TYPESCRIPT
interface EmailSummary {
  id: string;
  from: string;
  to: string[];
  subject: string;
  preview: string;          // First 128 characters of plain text
  hasAttachments: boolean;
  receivedAt: string;       // ISO timestamp
}

EmailDetail

Returned by emails.get() and emails.waitFor(). Contains the full email body, headers, and attachment metadata.

TYPESCRIPT
interface EmailDetail {
  id: string;
  messageId: string;
  from: string;
  to: string[];
  cc: string[] | null;
  bcc: string[] | null;
  subject: string;
  text: string | null;
  html: string | null;
  headers: Record<string, string>;
  hasAttachments: boolean;
  attachments: EmailAttachment[];
  receivedAt: string;       // ISO timestamp
}

EmailAttachment

TYPESCRIPT
interface EmailAttachment {
  id: string;
  filename: string;
  contentType: string;
  size: number;             // Size in bytes
  checksum: string;
}

Error Handling

The SDK throws typed errors for different failure modes:

TEXT
MailloopError (base class)
├── AuthenticationError  (401 - invalid or missing API key)
├── PermissionError      (403 - insufficient permissions)
├── NotFoundError        (404 - resource not found)
├── RateLimitError       (429 - too many requests)
├── TimeoutError         (waiting for email timed out)
└── ValidationError      (400 - invalid request data)

All errors extend MailloopError and include these properties:

PropertyTypeDescription
messagestringHuman-readable message
codestringMachine-readable code
statusnumber | undefinedHTTP status code
detailsRecord<string, unknown> | undefinedAdditional context

RateLimitError also includes a retryAfter property (seconds until the limit resets).

Example

TYPESCRIPT
import {
  MailloopClient,
  AuthenticationError,
  NotFoundError,
  RateLimitError,
  TimeoutError,
  ValidationError,
} from '@mailloop/sdk';

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

try {
  const email = await client.emails.waitFor('sandbox-id', {
    subject: 'Welcome',
    timeout: 15000
  });
} catch (error) {
  if (error instanceof TimeoutError) {
    console.log('No email arrived in time');
  } else if (error instanceof AuthenticationError) {
    console.log('Check your API key');
  } else if (error instanceof NotFoundError) {
    console.log('Sandbox does not exist');
  } else if (error instanceof RateLimitError) {
    console.log(`Rate limited. Retry after ${error.retryAfter}s`);
  } else if (error instanceof ValidationError) {
    console.log('Invalid parameters:', error.details);
  } else {
    throw error;
  }
}

REST API

The full interactive API documentation is available at api.mailloop.io (Swagger UI).

Authentication

All API requests require a Bearer token in the Authorization header:

TEXT
Authorization: Bearer ml_live_your_api_key_here

API keys are created in the Mailloop dashboard under Settings > API Keys.

Base URL

TEXT
https://api.mailloop.io/v1

Rate Limits

Rate limits are applied per API key:

EndpointLimitWindow
General200 req1 minute
Sandbox create30 req1 minute
Email list180 req1 minute
Email wait60 req1 minute
Email send60 req1 minute

Rate limit information is included in response headers:

HeaderDescription
X-RateLimit-LimitRequest limit per window
X-RateLimit-RemainingRemaining requests in current window
X-RateLimit-ResetUnix timestamp when the limit resets
Retry-AfterSeconds until the limit resets (on 429)