Misar Docs
MisarMailMisar.BlogMisarReachMisarPostMisar.DevMisar PlatformMisar IdentityMisar Posts API
Api Reference

Webhooks

Receive real-time event notifications for email opens, clicks, bounces, and more

MisarMail sends outbound webhooks to your endpoint when email events occur. Configure webhooks in Settings → Webhooks.

Configuration

Create a Webhook Endpoint

  1. Go to Settings → Webhooks → New Endpoint
  2. Enter your HTTPS URL
  3. Select event types to subscribe to
  4. Copy the signing secret

Event Types

EventDescription
email.sentEmail successfully sent via SMTP
email.deliveredDelivery confirmed by recipient server
email.openedRecipient opened the email
email.clickedRecipient clicked a tracked link
email.bouncedHard or soft bounce received
email.complainedSpam complaint (FBL report)
email.unsubscribedRecipient unsubscribed
email.receivedInbound email received on a configured domain
campaign.sentAll campaign emails have been queued
campaign.completedCampaign processing finished

Webhook Payload

All events share the same envelope format:

{
  "id": "evt_550e8400e29b41d4a716446655440000",
  "type": "email.opened",
  "created_at": "2026-02-17T12:00:00.000Z",
  "data": {
    "message_id": "msg_abc123",
    "email": "[email protected]",
    "campaign_id": "550e8400-e29b-41d4-a716-446655440001",
    "timestamp": "2026-02-17T12:00:00.000Z"
  }
}

Bounce Event

{
  "type": "email.bounced",
  "data": {
    "message_id": "msg_abc123",
    "email": "[email protected]",
    "bounce_type": "hard",
    "bounce_code": "550",
    "bounce_message": "User unknown"
  }
}

Signature Verification

Every webhook request includes an X-MisarMail-Signature header. Verify it to ensure the request is from MisarMail.

Verification Algorithm

  1. Read the raw request body as bytes
  2. Compute HMAC-SHA256 using your webhook signing secret
  3. Compare with the header value (constant-time comparison)
import crypto from "crypto";

export function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payload, "utf8")
    .digest("hex");

  const sigBuffer = Buffer.from(signature, "hex");
  const expBuffer = Buffer.from(expected, "hex");

  if (sigBuffer.length !== expBuffer.length) return false;

  return crypto.timingSafeEqual(sigBuffer, expBuffer);
}
// Next.js App Router route handler
export async function POST(req: Request) {
  const body = await req.text();
  const sig = req.headers.get("x-misarmail-signature") ?? "";
  const secret = process.env.MISARMAIL_WEBHOOK_SECRET!;

  if (!verifyWebhookSignature(body, sig, secret)) {
    return new Response("Invalid signature", { status: 401 });
  }

  const event = JSON.parse(body);

  switch (event.type) {
    case "email.bounced":
      await handleBounce(event.data);
      break;
    case "email.unsubscribed":
      await handleUnsubscribe(event.data);
      break;
    case "email.received":
      await handleInbound(event.data);
      break;
  }

  return new Response("OK");
}

Inbound Email Webhooks

When you configure an inbound domain (via POST /v1/inbound), MisarMail delivers incoming emails to your webhook endpoint as email.received events.

Inbound Payload

{
  "id": "evt_inbound_abc123",
  "type": "email.received",
  "created_at": "2026-02-17T12:00:00.000Z",
  "data": {
    "from": "[email protected]",
    "to": "[email protected]",
    "subject": "Help request",
    "text": "Hi, I need help with my account.",
    "html": "<p>Hi, I need help with my account.</p>",
    "headers": {
      "message-id": "<[email protected]>",
      "reply-to": "[email protected]"
    },
    "attachments": [
      {
        "filename": "screenshot.png",
        "content_type": "image/png",
        "size": 48210,
        "url": "https://api.misar.io/mail/v1/inbound/attachments/att_xyz"
      }
    ]
  }
}

Inbound Signature Verification

Inbound email webhooks use the same HMAC-SHA256 signature scheme. The X-MisarMail-Signature header is present on all inbound webhook requests. Verify it using the same verifyWebhookSignature function shown above with your inbound webhook secret.

Inbound webhooks require an active inbound domain configuration. See the Inbound Domains section for setup instructions.


Retry Policy

If your endpoint returns a non-2xx status or times out (30 seconds), MisarMail retries with exponential backoff:

AttemptDelay
1Immediate
21 minute
35 minutes
430 minutes
52 hours

After 5 failed attempts, the event is marked as failed and no further retries occur.

Best Practices

  • Respond with 200 immediately, process asynchronously
  • Use idempotency — the same event may be delivered more than once
  • Check event.id to deduplicate

Testing Webhooks

Use the Test button in Settings → Webhooks to send a test payload to your endpoint, or use a service like webhook.site during development.


Provider Webhooks (Inbound)

MisarMail receives delivery events from email providers and ISPs via these inbound webhook endpoints. These are for ISP/provider integrations, not for your app to consume — they're documented here for transparency and for self-hosted deployments.

Feedback Loop (FBL)

POST/api/webhooks/fbl

Feedback Loop (FBL) webhook — receives ARF (RFC 5965) complaint reports from ISPs (Yahoo/AOL, Microsoft JMRP, etc.).

When suppressed: true, the recipient is added to the suppression list and their contact status is set to complained. Duplicate FBL reports (same message ID) are idempotently ignored. A GET /api/webhooks/fbl health check returns { status: "ok", accepts: ["message/rfc822", "application/json", "text/plain"] }.

Auth: X-Webhook-Secret: FBL_WEBHOOK_SECRET header (or Authorization: Bearer FBL_WEBHOOK_SECRET).

Content-Types accepted:

  • message/rfc822 — raw ARF email
  • application/json — JSON wrapper { raw?, email?, message?, body? }
  • text/plain — raw ARF text

Response fields

successboolean

true when the report was processed.

feedbackTypestring

The ARF feedback type, e.g. abuse.

recipientstring

The complaining recipient's email address.

suppressedboolean

true when the recipient was added to the suppression list.

{
  "success": true,
  "feedbackType": "abuse",
  "recipient": "[email protected]",
  "suppressed": true
}

Mailcow delivery status

POST/api/webhooks/mailcow

Mailcow delivery status webhook. Receives bounce, delivery, and complaint events from the Mailcow SMTP server. Events are normalized and written to the email_events and bounce_events tables.

Auth: x-inbound-secret header matching MAILCOW_WEBHOOK_SECRET env var.

Request body

payloadobjectbodyrequired

Mailcow-formatted event JSON.

POST /api/webhooks/mailcow
x-inbound-secret: MAILCOW_WEBHOOK_SECRET

Postal mail server

POST/api/webhooks/postal

Postal mail server webhook. Receives delivery status, bounce, and spam reports from the Postal SMTP platform. Events handled: MessageSent, MessageDelivered, MessageBounced, MessageHeld, SpamReport.

Auth: IP allowlist + X-Postal-Signature header validation.

POST /api/webhooks/postal
X-Postal-Signature: <signature>

Auto-translate

POST/api/webhooks/auto-translate

Auto-translation webhook — processes incoming emails and auto-translates them for multilingual inbox support. Translates and updates the emails table.

Auth: Internal only (x-internal-secret header).

Request body

emailIdstringbodyrequired

ID of the email to translate.

targetLanguagestringbodyrequired

Language to translate the email into.

textstringbody

Plain-text email body to translate.

htmlstringbody

HTML email body to translate.

POST /api/webhooks/auto-translate
x-internal-secret: <secret>