← Back home

Documentation

Send your first email in five minutes.

If you have used Resend or Postmark before, this will feel familiar. If not, every step below is plain copy-paste.

Step 1

Generate an API key

Sign up, then visit /dashboard/api-keys/new. Save the key somewhere safe. We never show it again.

Step 2

Verify a sending domain

Go to /dashboard/domains/new, enter your domain, and publish the three DNS records (DKIM, SPF, DMARC) we show you. Click "Verify DNS now" once the records propagate.

Step 3

Send your first email

Via curl:

curl -X POST https://postdex.io/v1/emails \
  -H "Authorization: Bearer pdx_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme <hello@mail.acme.com>",
    "to": "user@example.com",
    "subject": "Hello",
    "html": "<p>Sent via Postdex.</p>"
  }'

Via the SDK:

import { Postdex } from "postdex";

const client = new Postdex(process.env.POSTDEX_API_KEY!);

await client.emails.send({
  from: "Acme <hello@mail.acme.com>",
  to: "user@example.com",
  subject: "Hello",
  html: "<p>Sent via Postdex.</p>",
});
Step 4

Idempotency

Pass an Idempotency-Key header (or idempotencyKey in the SDK) on every send. We cache the response for 24 hours so retries do not double-send.

Step 5

Receiving webhooks

Add a destination at /dashboard/webhooks/new. We POST JSON to your URL on every email event with an HMAC-SHA256 signature in X-Postdex-Signature.

// Verify in your webhook handler:
import { createHmac, timingSafeEqual } from "node:crypto";

const expected = createHmac("sha256", process.env.WEBHOOK_SECRET!)
  .update(rawBody)
  .digest("hex");
const provided = req.headers["x-postdex-signature"]?.replace(/^sha256=/, "");
if (!timingSafeEqual(Buffer.from(expected), Buffer.from(provided))) {
  return res.status(401).end();
}
Step 6

Rate limits

Free tier: 100 sends per rolling 7 days. Paid tiers raise that to the monthly quota of your plan. We return 429 with a Retry-After header when you exceed.

Step 7

Error shape

Every 4xx and 5xx response uses the same JSON shape:

{
  "name": "validation_error",
  "message": "Human-readable explanation.",
  "statusCode": 422
}