Skip to main content

Node.js SDK

The Node.js SDK is easiest to use with one mental model: create a temporary inbox, wait for the next email, then read the extracted field you need.

Install

npm install postmx

Super Simple Path

import { PostMX } from "postmx";

async function main() {
const postmx = new PostMX(process.env.POSTMX_API_KEY!);

const inbox = await postmx.createTemporaryInbox({
label: "signup-test",
});

console.log("Send your app email to:", inbox.email_address);

const message = await postmx.waitForMessage(inbox.id, {
timeoutMs: 30_000,
});

console.log("OTP:", message.otp);
console.log("First link:", message.links[0]?.url ?? null);
}

main().catch(console.error);

waitForMessage() returns the latest existing message immediately if the inbox already has one; otherwise it waits for the next incoming email until the timeout.

What Do You Want Back?

The default message already includes extracted fields like otp, links, and intent.

If you already have a message ID, contentMode is just a simple "what do you want back?" choice:

const otpOnly = await postmx.getMessage("msg_123", "otp");
console.log(otpOnly.otp);

const linksOnly = await postmx.getMessage("msg_123", "links");
console.log(linksOnly.links);

Supported modes:

  • full: full message detail
  • otp: message metadata plus otp
  • links: message metadata plus links
  • text_only: message metadata plus text_body

Create the client

import { PostMX } from "postmx";

const postmx = new PostMX("pmx_live_...");

Constructor options

OptionTypeDefaultNotes
baseUrlstringhttps://api.postmx.coOverride for local or staged environments.
maxRetriesnumber2Retries apply to 429, 500, 502, 503, and 504.
timeoutnumber30000Request timeout in milliseconds.

Advanced

Lifecycle controls

const inbox = await postmx.createInbox({
label: "checkout-flow",
lifecycle_mode: "temporary",
ttl_minutes: 15,
});

createInbox() accepts:

  • label: Human-readable name for the inbox.
  • lifecycle_mode: temporary or persistent.
  • ttl_minutes: Optional TTL for temporary inboxes. Current API limits are 10 to 60.

POST requests are safe to retry because the SDK automatically adds an Idempotency-Key if you do not pass one yourself.

List inboxes and wildcard addresses

const { inboxes, pageInfo, wildcard_address } = await postmx.listInboxes({
limit: 20,
});

listInboxes() returns:

  • inboxes: The current page of inboxes.
  • pageInfo: Pagination metadata with has_more and next_cursor.
  • wildcard_address: A wildcard address object when available, otherwise null.

List messages for an inbox

const { messages, pageInfo } = await postmx.listMessages(inbox.id, {
limit: 10,
});

List messages for a recipient email

const { messages, pageInfo } = await postmx.listMessagesByRecipient(
"[email protected]",
{ limit: 10 },
);

Get a message

const message = await postmx.getMessage("msg_123");

console.log(message.subject);
console.log(message.text_body);
console.log(message.html_body);
console.log(message.otp);
console.log(message.links);

Wait for a message

const message = await postmx.waitForMessage(inbox.id, {
intervalMs: 1_000,
timeoutMs: 60_000,
});

Notes:

  • intervalMs defaults to 1000.
  • timeoutMs defaults to 60000.
  • intervalMs must be at least 200.
  • waitForMessage() returns the latest existing message immediately if the inbox already has one; otherwise it waits for the next incoming email.

Create a webhook

const result = await postmx.createWebhook({
label: "production-events",
target_url: "https://example.com/webhooks/postmx",
});

console.log(result.webhook.id);
console.log(result.signing_secret);

You can also scope a webhook to one inbox:

await postmx.createWebhook({
label: "signup-only",
target_url: "https://example.com/webhooks/postmx",
inbox_id: inbox.id,
});

Use a final public HTTPS endpoint. The API rejects localhost targets, embedded credentials, and private or reserved IP literals. PostMX does not follow redirects, and each delivery attempt times out after 10 seconds.

Verify a webhook signature

import { verifyWebhookSignature } from "postmx";

const event = verifyWebhookSignature({
payload: rawBody,
signature:
(req.headers["x-postmx-signature"] as string) ??
(req.headers["postmx-signature"] as string),
timestamp: req.headers["x-postmx-timestamp"] as string,
signingSecret: process.env.POSTMX_WEBHOOK_SECRET!,
});

console.log(event.type);
console.log(event.data.message.otp);

Important:

  • Store the returned signing_secret immediately. It is returned once.
  • Pass the raw request body, not parsed JSON.
  • Read X-PostMX-Delivery-Id for attempt tracing and X-PostMX-Event-Id for diagnostics.
  • The signature format is v1=<base64url(hmac_sha256(signing_secret, timestamp + "." + raw_body))>.
  • The default timestamp tolerance is 300 seconds.
  • The payload already includes the full message plus extracted fields such as otp, links, and intent.

Error handling

import { PostMXApiError, PostMXNetworkError } from "postmx";

try {
await postmx.getMessage("msg_missing");
} catch (error) {
if (error instanceof PostMXApiError) {
console.log(error.status);
console.log(error.code);
console.log(error.message);
console.log(error.requestId);
console.log(error.retryAfterSeconds);
} else if (error instanceof PostMXNetworkError) {
console.log(error.cause.message);
}
}

Method reference

new PostMX(apiKey, options?)
postmx.listInboxes(params?)
postmx.createInbox(params, options?)
postmx.createTemporaryInbox(params, options?)
postmx.listMessages(inboxId, params?)
postmx.listMessagesByRecipient(recipientEmail, params?)
postmx.getMessage(messageId, contentMode?)
postmx.createWebhook(params, options?)
postmx.waitForMessage(inboxId, options?)

Good defaults

  • Use createTemporaryInbox() for the beginner path, then drop down to createInbox() when you need lifecycle controls.
  • Use postmx.getMessage(messageId, "otp") or postmx.getMessage(messageId, "links") when you only need one extracted field.
  • Keep maxRetries enabled unless you already have higher-level retry logic.
  • Store the webhook signing_secret when a webhook is created. It is the value you need for signature verification later.