The official Mailloop SDK for JavaScript and TypeScript. Zero dependencies, full type safety, and dual ESM/CommonJS builds.
Installation
# 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.
import { MailloopClient } from '@mailloop/sdk';
const client = new MailloopClient({
apiKey: 'ml_live_your_api_key_here'
}); Options
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
apiKey | string | Yes | -- | API key from the dashboard |
baseUrl | string | No | https://api.mailloop.io/v1 | API base URL |
timeout | number | No | 30000 | Request timeout in milliseconds |
Sandboxes
Manage email testing sandboxes.
client.sandboxes.create(params)
Create a new persistent sandbox.
const sandbox = await client.sandboxes.create({
name: 'my-sandbox'
}); Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Sandbox 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.
const sandbox = await client.sandboxes.createTemporary({
duration: 30,
name: 'ci-test'
}); Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
duration | number | Yes | Seconds until auto-deletion (1–60) |
name | string | No | Optional sandbox name |
Returns: Sandbox with isTemporary: true and deleteAfter set.
client.sandboxes.list()
List all sandboxes for your organization.
const { sandboxes } = await client.sandboxes.list(); Returns: { sandboxes: Sandbox[] }
client.sandboxes.get(id)
Get a sandbox by ID.
const sandbox = await client.sandboxes.get('sandbox-id'); const updated = await client.sandboxes.update('sandbox-id', {
name: 'renamed-sandbox'
}); Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
name | string | No | New sandbox name |
Returns: Sandbox
client.sandboxes.delete(id)
Permanently delete a sandbox and all its emails.
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).
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:
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
limit | number | No | 20 | Max emails to return (1–100) |
offset | number | No | 0 | Number of emails to skip |
after | string | No | -- | Return emails after this email ID |
Returns: { emails: EmailSummary[], total: number }
client.emails.get(sandboxId, emailId)
Get the full details of a specific email, including HTML body, headers, and attachments.
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 to arrive. The server polls internally and returns when a match is found.
// 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:
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
timeout | number | No | 30000 | Max wait time in ms (max 60000) |
from | string | No | -- | Filter by sender address |
to | string | No | -- | Filter by recipient address |
subject | string | No | -- | Filter by subject (substring match) |
Returns: EmailDetail
Throws: TimeoutError if no matching email arrives within the timeout.
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)
Note: This method is experimental and may change without notice.
Send a transactional email with block-based content.
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
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.
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.
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
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:
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:
| Property | Type | Description |
|---|---|---|
message | string | Human-readable message |
code | string | Machine-readable code |
status | number | undefined | HTTP status code |
details | Record<string, unknown> | undefined | Additional context |
RateLimitError also includes a retryAfter property (seconds until the limit resets).
Example
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:
Authorization: Bearer ml_live_your_api_key_here API keys are created in the Mailloop dashboard under Settings > API Keys.
Base URL
https://api.mailloop.io/v1 Rate Limits
Rate limits are applied per API key:
| Endpoint | Limit | Window |
|---|---|---|
| General | 200 req | 1 minute |
| Sandbox create | 30 req | 1 minute |
| Email list | 180 req | 1 minute |
| Email wait | 60 req | 1 minute |
| Email send | 60 req | 1 minute |
Rate limit information is included in response headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Request limit per window |
X-RateLimit-Remaining | Remaining requests in current window |
X-RateLimit-Reset | Unix timestamp when the limit resets |
Retry-After | Seconds until the limit resets (on 429) |