> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://agentinbox.space/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://agentinbox.space/_mcp/server.

# События вебхуков

Как сказано в обзоре, вебхуки позволяют строить приложения, управляемые событиями.

Agent Inbox поддерживает несколько типов событий для полноценных событийных сценариев почтовых агентов.

Все полезные нагрузки вебхуков имеют одну базовую структуру:

```json
{
  "type": "event",
  "event_type": "event.name",
  "event_id": "evt_123abc..."
  // ... event-specific data object
}
```

## Разбор событий через SDK

В SDK Agent Inbox есть типизированные классы для каждого события вебхука — сырые полезные нагрузки можно разобрать в типизированные объекты.

**`TypeScript`**

```typescript title="TypeScript"
import { serialization  } from "agentinbox";

async function handleWebhook(payload: Record<string, unknown>) {
  const eventType = payload.event_type;

  if (eventType === "message.received") {
    const event = await serialization.events.MessageReceivedEvent.parse(payload);
    // access typed fields
    console.log(event.message.subject);
    console.log(event.thread.messageCount);
  } else if (eventType === "message.sent") {
    const event = await serialization.events.MessageSentEvent.parse(payload);
    console.log(event.send.recipients);
  } else if (eventType === "message.bounced") {
    const event = await serialization.events.MessageBouncedEvent.parse(payload);
    console.log(event.bounce.type);
  } else if (eventType === "message.delivered") {
    const event = await serialization.events.MessageDeliveredEvent.parse(payload);
    console.log(event.delivery.recipients);
  } else if (eventType === "message.complained") {
    const event = await serialization.events.MessageComplainedEvent.parse(payload);
    console.log(event.complaint.type);
  } else if (eventType === "message.rejected") {
    const event = await serialization.events.MessageRejectedEvent.parse(payload);
    console.log(event.reject.reason);
  } else if (eventType === "domain.verified") {
    const event = await serialization.events.DomainVerifiedEvent.parse(payload);
    console.log(event.domain.status);
  }
}
```

**`Python`**

```python title="Python"
from agentinbox import (
    MessageReceivedEvent,
    MessageSentEvent,
    MessageBouncedEvent,
    MessageDeliveredEvent,
    MessageComplainedEvent,
    MessageRejectedEvent,
    DomainVerifiedEvent,
)

def handle_webhook(payload: dict):
    event_type = payload.get("event_type")

    if event_type == "message.received":
        event = MessageReceivedEvent(**payload)
        # access typed fields
        print(event.message.subject)
        print(event.thread.message_count)
    elif event_type == "message.sent":
        event = MessageSentEvent(**payload)
        print(event.send.recipients)
    elif event_type == "message.bounced":
        event = MessageBouncedEvent(**payload)
        print(event.bounce.type)
    elif event_type == "message.delivered":
        event = MessageDeliveredEvent(**payload)
        print(event.delivery.recipients)
    elif event_type == "message.complained":
        event = MessageComplainedEvent(**payload)
        print(event.complaint.type)
    elif event_type == "message.rejected":
        event = MessageRejectedEvent(**payload)
        print(event.reject.reason)
    elif event_type == "domain.verified":
        event = DomainVerifiedEvent(**payload)
        print(event.domain.status)
```

## Копировать в Cursor / Claude

Скопируйте один из блоков ниже для разбора событий вебхука за один раз.

**`Python`**

```python title="Python"
"""
Agent Inbox Webhook Events — copy into Cursor/Claude.

Parse typed events: MessageReceivedEvent (has message+thread), MessageSentEvent (send), MessageBouncedEvent (bounce),
MessageDeliveredEvent (delivery), MessageComplainedEvent (complaint), MessageRejectedEvent (reject), DomainVerifiedEvent (domain).
Only message.received includes full message+thread; others have send/delivery/bounce/complaint/reject/domain.
"""
from agentinbox import (
  MessageReceivedEvent, MessageSentEvent, MessageBouncedEvent,
  MessageDeliveredEvent, MessageComplainedEvent, MessageRejectedEvent, DomainVerifiedEvent,
)

def handle(payload: dict):
  t = payload.get("event_type")
  if t == "message.received": e = MessageReceivedEvent(**payload); print(e.message.subject, e.thread.message_count)
  elif t == "message.sent": e = MessageSentEvent(**payload); print(e.send.recipients)
  elif t == "message.bounced": e = MessageBouncedEvent(**payload); print(e.bounce.type)
  elif t == "message.delivered": e = MessageDeliveredEvent(**payload)
  elif t == "message.complained": e = MessageComplainedEvent(**payload)
  elif t == "message.rejected": e = MessageRejectedEvent(**payload); print(e.reject.reason)
  elif t == "domain.verified": e = DomainVerifiedEvent(**payload); print(e.domain.status)
```

**`TypeScript`**

```typescript title="TypeScript"
/**
 * Agent Inbox Webhook Events — copy into Cursor/Claude.
 *
 * Parse with serialization.events.MessageReceivedEvent.parse(payload), etc.
 * Only message.received has message+thread; others have send/delivery/bounce/complaint/reject/domain.
 */
import { serialization  } from "agentinbox";

async function handle(payload: Record<string, unknown>) {
  const t = payload.event_type;
  if (t === "message.received") {
    const e = await serialization.events.MessageReceivedEvent.parse(payload);
    console.log(e.message.subject, e.thread.messageCount);
  } else if (t === "message.sent") {
    const e = await serialization.events.MessageSentEvent.parse(payload);
    console.log(e.send.recipients);
  } else if (t === "message.bounced") {
    const e = await serialization.events.MessageBouncedEvent.parse(payload);
    console.log(e.bounce.type);
  } else if (t === "message.delivered") {
    const e = await serialization.events.MessageDeliveredEvent.parse(payload);
    console.log(e.delivery);
  } else if (t === "message.complained") {
    const e = await serialization.events.MessageComplainedEvent.parse(payload);
    console.log(e.complaint);
  } else if (t === "message.rejected") {
    const e = await serialization.events.MessageRejectedEvent.parse(payload);
    console.log(e.reject);
  } else if (t === "domain.verified") {
    const e = await serialization.events.DomainVerifiedEvent.parse(payload);
    console.log(e.domain);
  }
}
```

## События сообщений

### `message.received`

* **Описание:** срабатывает, когда новое письмо успешно получено и обработано в одном из ваших ящиков (`Inboxes`). Чаще всего это триггер для запуска сценариев агента.
* **Пример сценария:** запуск внутреннего процесса, когда жалоба клиента попадает в ящик поддержки

> **Note**
>
> Важно: только у `message.received` в полезной нагрузке есть и метаданные **цепочки** (`Thread`), и **сообщения** (`Message`). У остальных типов событий в теле только метаданные самого события. Если нужны метаданные и для других типов — напишите на [info@patternautomation.com](mailto:info@patternautomation.com).

> **Warning**
>
> Поля `text` и `preview` могут отсутствовать, если исходное письмо содержит только HTML без текстовой части. Так бывает у пересланных писем из Gmail и Outlook. При обработке входящих опирайтесь в первую очередь на `html`, а `text` считайте необязательным.

```json
{
  "type": "event",
  "event_type": "message.received",
  "event_id": "evt_123abc",
  "message": {
    "inbox_id": "inbox_456def",
    "thread_id": "thd_789ghi",
    "message_id": "<abc123@agentinbox.space>",
    "labels": ["received"],
    "timestamp": "2023-10-27T10:00:00Z",
    "from": "Jane Doe <jane@example.com>",
    "to": ["Support Agent <info@patternautomation.com>"],
    "subject": "Question about my account",
    "preview": "A short preview of the email text...",
    "text": "The full text body of the email.",
    "html": "<html>...</html>",
    "size": 2048,
    "updated_at": "2023-10-27T10:00:00Z",
    "created_at": "2023-10-27T10:00:00Z"
  },
  "thread": {
    "inbox_id": "inbox_456def",
    "thread_id": "thd_789ghi",
    "labels": ["received"],
    "timestamp": "2023-10-27T10:00:00Z",
    "senders": ["Jane Doe <jane@example.com>"],
    "recipients": ["Support Agent <info@patternautomation.com>"],
    "subject": "Question about my account",
    "preview": "A short preview of the email text...",
    "last_message_id": "<abc123@agentinbox.space>",
    "message_count": 1,
    "size": 2048,
    "updated_at": "2023-10-27T10:00:00Z",
    "created_at": "2023-10-27T10:00:00Z"
  }
}
```

### `message.sent`

* **Описание:** срабатывает, когда сообщение успешно отправлено из одного из ваших ящиков.
* **Сценарий:** учёт исходящих, обновление БД или последующие шаги после отправки.

```json
{
  "type": "event",
  "event_type": "message.sent",
  "event_id": "evt_456def",
  "send": {
    "inbox_id": "inbox_456def",
    "thread_id": "thd_789ghi",
    "message_id": "<abc123@agentinbox.space>",
    "timestamp": "2023-10-27T10:05:00Z",
    "recipients": [
      "recipient@example.com"
    ]
  }
}
```

### `message.delivered`

* **Описание:** срабатывает, когда отправленное письмо успешно доставлено на почтовый сервер получателя.
* **Сценарий:** подтверждение доставки, обновление статуса, сценарии подтверждения доставки.

```json
{
  "type": "event",
  "event_type": "message.delivered",
  "event_id": "evt_789ghi",
  "delivery": {
    "inbox_id": "inbox_456def",
    "thread_id": "thd_789ghi",
    "message_id": "<abc123@agentinbox.space>",
    "timestamp": "2023-10-27T10:06:00Z",
    "recipients": [
      "recipient@example.com"
    ]
  }
}
```

#### В чём разница между \`message.sent\` и \`message.delivered\`?

`message.sent` значит, что письмо успешно ушло с наших серверов и путешествует по сети. Обычно это происходит раньше, чем `message.delivered`: последнее значит, что принимающий сервер (Gmail, Outlook и т. п.) ответил `200 OK` — письмо им принято. Что они с ним дальше делают — нам неизвестно.

#### Значит ли \`message.delivered\`, что письмо попало во «Входящие»?

Нет. Как сказано выше, `message.delivered` значит, что почтовый сервер получателя сообщил: «Мы получили письмо, дальше сами». У провайдеров свои алгоритмы — попадёт ли письмо во входящие или в спам. Со своей стороны мы делаем всё нужное, чтобы письма выглядели для провайдеров как легитимные для основного ящика.

### `message.bounced`

* **Описание:** срабатывает, когда отправка не удалась и письмо отскочило. Включает тип и подтип отскока.
* **Сценарий:** обработка отскоков, обновление статуса получателя, сценарии после bounce.

```json
{
  "type": "event",
  "event_type": "message.bounced",
  "event_id": "evt_012jkl",
  "bounce": {
    "inbox_id": "inbox_456def",
    "thread_id": "thd_789ghi",
    "message_id": "<abc123@agentinbox.space>",
    "timestamp": "2023-10-27T10:07:00Z",
    "type": "Permanent",
    "sub_type": "General",
    "recipients": [
      {
        "address": "invalid@example.com",
        "status": "bounced"
      }
    ]
  }
}
```

### `message.complained`

* **Описание:** срабатывает, когда получатель пометил письмо как спам или подал жалобу.
* **Сценарий:** реакция на жалобы, репутация отправителя, внутренние процессы по жалобам.

```json
{
  "type": "event",
  "event_type": "message.complained",
  "event_id": "evt_345mno",
  "complaint": {
    "inbox_id": "inbox_456def",
    "thread_id": "thd_789ghi",
    "message_id": "<abc123@agentinbox.space>",
    "timestamp": "2023-10-27T10:08:00Z",
    "type": "abuse",
    "sub_type": "spam",
    "recipients": [
      "complainer@example.com"
    ]
  }
}
```

### `message.rejected`

* **Описание:** срабатывает, когда отправка отклонена до фактической отправки — обычно из‑за ошибок валидации или политики.
* **Сценарий:** логирование причин, сценарии валидации.

```json
{
  "type": "event",
  "event_type": "message.rejected",
  "event_id": "evt_678pqr",
  "reject": {
    "inbox_id": "inbox_456def",
    "thread_id": "thd_789ghi",
    "message_id": "<abc123@agentinbox.space>",
    "timestamp": "2023-10-27T10:09:00Z",
    "reason": "Invalid recipient address"
  }
}
```

> **Warning**
>
> Если вы отправляете на адрес со статусом `bounced`, `rejected` или после `complained`, мы блокируем дальнейшую отправку на этот адрес, чтобы держать низкий уровень отскоков. Следите, чтобы доля отскоков аккаунта была ниже 4%, иначе аккаунт может попасть на проверку.

## События домена

### `domain.verified`

* **Описание:** срабатывает, когда домен успешно верифицирован и готов к отправке.
* **Сценарий:** автоматическое включение функций домена, обновление статуса, шаги после верификации.

```json
{
  "type": "event",
  "event_type": "domain.verified",
  "event_id": "evt_901stu",
  "domain": {
    "domain_id": "dom_123abc",
    "status": "verified",
    "feedback_enabled": true,
    "records": [
      // ... DNS verification records
    ],
    "created_at": "2023-10-27T09:00:00Z",
    "updated_at": "2023-10-27T10:00:00Z"
  }
}
```

## Фильтрация событий

При создании вебхука можно указать, на какие события подписываться. Это позволяет:

* снизить трафик вебхуков, подписавшись только на нужное;
* завести отдельные вебхуки под отдельные сценарии.

Например, только на входящие — `message.received`. Для трекинга доставки — `message.sent`, `message.delivered` и `message.bounced`.