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.