TypeScript SDK
The official TypeScript SDK for Receiver.email. Typed, promise based, and built around minting an inbox and waiting for what lands in it.
Installation
npm install @receiverdotemail/sdkAlso works with yarn and pnpm:
yarn add @receiverdotemail/sdkpnpm add @receiverdotemail/sdkConfiguration
import { ReceiverClient } from "@receiverdotemail/sdk";
// Reads RECEIVER_API_KEY (and RECEIVER_API_URL) from the environment
const client = new ReceiverClient();
// Or pass everything explicitly
const client = new ReceiverClient({
apiKey: process.env.RECEIVER_API_KEY, // recv_live_...
baseUrl: "https://api.receiver.email/api/v1", // Optional
timeout: 30000, // Optional: per-request ms
userAgent: "my-tool/1.0", // Optional
});With an API key, projectId is optional on every email and inbox method: the client calls whoami() once and remembers the key's project. Pass projectId (to the constructor or a method) to skip that request or to use a dashboard token.
The Recommended Pattern
Mint an address, use it, then wait from its createdAt. Mail from before the address existed can never match, so retries and parallel runs stay independent.
const inbox = await client.inboxes.create({ prefix: "signup" });
await app.signUp(inbox.address);
const otp = await client.emails.waitForOtp({
recipient: inbox.address,
since: inbox.createdAt,
timeout: 60,
});
await app.verify(otp.code);Inboxes and Identity
inboxes.create(): Mint an Address
const inbox = await client.inboxes.create({ prefix: "signup" }); // prefix optional
// {
// address: "[email protected]",
// prefix: "signup-k2m9qa",
// projectId: "proj_abc123",
// createdAt: "2026-11-05T10:00:00.000Z"
// }whoami(): Who the Key Belongs To
const me = await client.whoami();
if (me.authType === "apikey") {
console.log(me.project.name, me.project.emailSuffix); // "Signup tests", "[email protected]"
}Waiting for Email
waitFor(): Long-Poll for an Email
Resolves with the first email matching the filters received after since. timeout is in seconds, 30 by default, and can be any length: the SDK repeats 60 second server waits until it runs out, then throws ReceiverTimeoutError. It never resolves to null.
const email = await client.emails.waitFor({
recipient: inbox.address, // Optional filters
sender: "[email protected]",
subject: "Reset", // Substring, case-insensitive
since: inbox.createdAt, // Default: the last 60 seconds
timeout: 60, // Seconds (default 30)
});
console.log(email.subject);
console.log(email.links); // Every URL in the email
console.log(email.textBody); // get() adds HTML, headers and attachmentswaitForOtp(): Wait for a Code
Same filters and looping as waitFor(), plus codeLength (4, 6 or 8). Returns the newest code, where it was found, the email it came from and every other code in the matching emails.
const otp = await client.emails.waitForOtp({
recipient: inbox.address,
since: inbox.createdAt,
codeLength: 6, // Optional
timeout: 60,
});
otp.code; // "847291"
otp.source; // "subject" | "body"
otp.email; // { id, sendTo, sender, subject, receivedAt, ... }
otp.candidates; // Every code found, newest firstgetOtp(): Check Without Waiting
const otp = await client.emails.getOtp({ recipient: inbox.address, since: inbox.createdAt });
if (otp === null) {
// No code yet
}Timeouts
import { ReceiverTimeoutError } from "@receiverdotemail/sdk";
try {
await client.emails.waitFor({ recipient: inbox.address, since: inbox.createdAt, timeout: 30 });
} catch (error) {
if (error instanceof ReceiverTimeoutError) {
console.log(error.since); // The since the API used
console.log(error.waitedSeconds); // How long it waited
}
throw error;
}To wait for the next email on an address, pass the receivedAt of the one you already have as since.
stream(): Emails as They Arrive
An async iterator over new emails (Server-Sent Events). It reconnects on its own and never repeats or skips an email; break out of the loop to close it.
for await (const event of client.emails.stream({ recipient: inbox.address })) {
console.log(event.email.subject, event.otp?.code);
if (event.otp) break;
}sendTest(): Send Yourself a Test Email
const inbox = await client.inboxes.create();
await client.emails.sendTest({ to: inbox.address }); // 10 per project per hour
const otp = await client.emails.waitForOtp({ recipient: inbox.address, since: inbox.createdAt });Reading Email
list() and listAll()
const page = await client.emails.list({
recipient: "[email protected]", // Optional filters
receivedAfter: new Date("2026-11-01"),
limit: 50, // Max 100
});
if (page.hasMore) {
const next = await client.emails.list({ cursor: page.nextCursor });
}
// Or iterate every page
for await (const email of client.emails.listAll({ sender: "[email protected]" })) {
console.log(email.subject);
}get() and delete()
const email = await client.emails.get("email_xyz789");
console.log(email.htmlBody, email.headers, email.attachments);
await client.emails.delete("email_xyz789");
// With a dashboard token, or another project, pass the project first
await client.emails.get("proj_abc123", "email_xyz789");Convenience Methods
await client.emails.findByRecipient("[email protected]");
await client.emails.findBySender("[email protected]");
await client.emails.findBySubject("Password Reset");Webhooks
Managing webhooks needs a dashboard access token, not an API key (see the API reference). Verifying deliveries works anywhere:
import { verifyWebhookSignature } from "@receiverdotemail/sdk";
const ok = verifyWebhookSignature({
secret: process.env.RECEIVER_WEBHOOK_SECRET!, // whsec_...
header: req.headers["receiver-signature"],
body: rawBody, // The raw request body string
});
// Managing webhooks: a client made with a dashboard access token
const admin = new ReceiverClient({ apiKey: accessToken });
const hook = await admin.webhooks.create({ projectId: "proj_abc123", url: "https://example.com/hooks/receiver" });
await admin.webhooks.list("proj_abc123");
await admin.webhooks.test("proj_abc123", hook.id);
await admin.webhooks.deliveries("proj_abc123", hook.id);
await admin.webhooks.delete("proj_abc123", hook.id);Projects, Organizations and API Keys
const project = await client.projects.get("proj_abc123");
console.log(project.emailSuffix); // "[email protected]"
// These need a dashboard access token
await client.projects.list("org_abc123");
await client.projects.create({ organizationId: "org_abc123", name: "CI Tests" });
await client.organizations.list();
const key = await client.apiKeys.create({ projectId: "proj_abc123", name: "CI Pipeline" });
console.log(key.key); // Full key, only shown once
await client.apiKeys.revoke("proj_abc123", key.id);Error Handling
import {
ReceiverError,
ReceiverTimeoutError,
RateLimitError,
AuthenticationError,
NotFoundError,
} from "@receiverdotemail/sdk";
try {
await client.emails.waitForOtp({ recipient, since });
} catch (error) {
if (error instanceof ReceiverTimeoutError) {
// Nothing arrived in time
} else if (error instanceof RateLimitError) {
console.log("Retry at", error.resetAt);
} else if (error instanceof AuthenticationError) {
// Bad or revoked key
} else if (error instanceof NotFoundError) {
// Wrong project or email id
} else if (error instanceof ReceiverError) {
console.log(error.statusCode, error.code, error.message);
}
}Upgrading to 2.0
waitFor()throwsReceiverTimeoutErrorinstead of resolving tonull.sincereplacesreceivedAfteron waits (the old name still works).projectIdis optional with an API key.