webhook events.md


title: "Webhook events" description: "Every MachineMail webhook event type and its payload, the event envelope, delivery retries and replay, and how to verify the signature on each delivery." canonical: "https://machinemail.dev/docs/reference/webhook-events"

Webhook events

Every MachineMail webhook event type and its payload, the event envelope, delivery retries and replay, and how to verify the signature on each delivery.

Source: https://machinemail.dev/docs/reference/webhook-events

MachineMail records an event when mail arrives, is sent, is delivered or bounces, and when inboxes, domains and drafts change. Each event is:

Event types

Event type When
message.received Mail arrived and the trust layer found nothing to flag.
message.received.quarantined Mail arrived with trust level quarantined (prompt injection, phishing, or a blocked sender). Its INBOX label is replaced by QUARANTINE.
message.received.spam Mail arrived that the classifier scored as spam (is_spam at least 0.9). INBOX is replaced by SPAM.
message.received.unauthenticated Mail arrived that failed DMARC, or failed both SPF and DKIM. UNAUTHENTICATED is added; INBOX stays.
message.received.blocked Mail arrived from a sender on a receive block list. Its only label is BLOCKED.
message.sent A message was sent: send, reply, reply-all, forward or draft send.
message.delivered The receiving server accepted a sent message.
message.bounced A sent message bounced. Permanent bounces add the recipients to suppressions.
message.complained A recipient marked a sent message as spam. The recipient is suppressed.
message.rejected Mail arrived for a paused inbox and was not stored.
domain.verified A custom domain passed DNS verification.
domain.updated A DNS check changed a custom domain's status or a record's status. Becoming VERIFIED also fires domain.verified.
inbox.created An inbox was created, through the API, MCP, the dashboard, or a catch-all domain.
inbox.expired An ephemeral inbox reached expires_at and was deleted with its mail.
draft.sent A draft was sent, by request or on its send_at schedule.
webhook.test Sent only by POST /v0/webhooks/{webhook_id}/test, to that one webhook.

An inbound message fires exactly one message.received* event. The checks run in this order and the first match wins: blocked sender, quarantined, spam, unauthenticated, otherwise message.received.

When you create a webhook, event_types picks the types it receives. Omitting it, or sending an empty array, subscribes it to every type. An unknown type returns 400 Unknown event type: <type>.

MachineMail does not emit AgentMail's message.opened or calendar events.

Envelope

Every webhook body, SSE data: line and WebSocket event frame is the same JSON envelope:

{
  "type": "event",
  "event_id": "evt_01k6x3g4pt0000k6t2wa8e1n3q",
  "event_type": "message.received",
  "timestamp": "2026-10-01T12:00:00.000Z",
  "message": { "...": "the payload keys, at the top level" },
  "thread": { "...": "..." },
  "data": {
    "message": { "...": "the same payload, again under data" },
    "thread": { "...": "..." }
  }
}
Field Type Meaning
type `