YourMail

Guides

Webhooks

POSTyour-endpoint

YourMail can push real-time delivery events to your server as signed HTTP POST requests. Register a webhook endpoint in the dashboard and YourMail will notify you when an email is sent, delivered, bounced, or complained about.

Why use this

Polling GET /v1/emails/:id works for occasional status checks, but webhooks let you react to delivery outcomes as they happen without keeping a poll loop running. This is how you keep your own database in sync with what YourMail knows.

For example

When alice@example.com hard-bounces, YourMail fires email.bounced at your endpoint within seconds — so your app can immediately suppress her address and avoid burning your sender reputation.

Register an endpoint

Go to Webhooks in the dashboard, click Add endpoint, and paste your HTTPS URL. YourMail will show you a signing secret — copy it into your server as YOURMAIL_WEBHOOK_SECRET. You can register multiple endpoints and choose which event types each one receives.

Event types

Every event has a type field. The full taxonomy:

email.sent
YourMail accepted the email and handed it to AWS SES.
email.delivered
The receiving mail server confirmed delivery.
email.opened
The recipient opened the email (requires open tracking to be enabled). May fire more than once.
email.clicked
The recipient clicked a tracked link in the email (requires click tracking to be enabled). May fire more than once.
email.bounced
A hard bounce was returned. The recipient address is automatically suppressed.
email.complained
The recipient filed a spam complaint. The address is automatically suppressed.
email.delivery_delayed
SES is retrying delivery due to a transient failure (soft bounce). A later delivered or bounced event will follow.
email.failed
The email could not be submitted to SES — e.g. the from address failed DKIM signing after dispatch.

Payload shape

Every event POST body is a JSON object. The data field carries the email metadata. For email.bounced and email.complained, data also includes the affected recipient and a reason string from the mail server.

// Every webhook POST body has this shape:
{
  "id": "evt_01j9abc123def456",          // unique event ID — dedupe on this
  "type": "email.delivered",             // event type
  "created_at": "2026-06-30T12:00:00.000Z",  // ISO 8601 timestamp
  "data": {
    "email_id": "jn7abc123def456",      // the email this event belongs to
    "from": "hello@mail.acme.com",
    "to": ["alice@example.com"],
    "subject": "Your receipt",
    "tags": [{ "name": "category", "value": "receipt" }]
  }
}

At-least-once · not ordered

Events are delivered at least once and are not guaranteed to arrive in order — your handler can receive email.delivered before email.sent. Dedupe on the event id; if you need sequencing, sort by created_at. Your endpoint must return a 2xx response within 10 seconds or YourMail will retry.

Signature verification

Every request includes a yourmail-signature header in the form t=<unix>,v1=<hmac-hex>. The HMAC is SHA-256 over "<timestamp>.<rawBody>" using your webhook secret as the key. Always verify this before processing an event — it proves the request came from YourMail, not a third party.

The easiest path is the SDK helper:

import { verifyWebhookSignature } from "yourmail";

// In your handler — pass the RAW request body, not a parsed object.
const ok = await verifyWebhookSignature({
  secret: process.env.YOURMAIL_WEBHOOK_SECRET!,
  header: req.headers["yourmail-signature"],
  payload: rawBody,
});
if (!ok) return res.status(400).end();

Important — pass the raw request body (a string or Buffer), not a parsed JSON.parse result. Body parsers in Express or Next.js may reformat whitespace, which invalidates the signature.

Full handler example

A minimal Next.js App Router route that verifies the signature and branches on event type:

// app/api/webhooks/yourmail/route.ts  (Next.js App Router)
import { verifyWebhookSignature } from "yourmail";
import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
  const rawBody = await req.text();
  const header = req.headers.get("yourmail-signature") ?? "";

  const ok = await verifyWebhookSignature({
    secret: process.env.YOURMAIL_WEBHOOK_SECRET!,
    header,
    payload: rawBody,
  });

  if (!ok) {
    return NextResponse.json({ error: "Invalid signature" }, { status: 400 });
  }

  const event = JSON.parse(rawBody);

  switch (event.type) {
    case "email.delivered":
      // mark order as sent in your DB
      break;
    case "email.bounced":
      // suppress or notify the recipient
      break;
    case "email.complained":
      // unsubscribe the recipient
      break;
  }

  return NextResponse.json({ received: true });
}