---
title: email.received
description: Inbound mail arrived and was matched onto a thread.
sidebar:
  label: email.received
  icon: inbox
---

Inbound mail. The one most applications are built around — it fires once per received message, with the thread it belongs to already resolved.

## Payload

| Prop | Type | Default | Description |
| - | - | - | - |
| `email_id` | `string` | - | eml_… — retrieve it for bodies and headers. |
| `thread_id` | `string` | - | The conversation it belongs to. |
| `domain_id` | `string \| null` | - | The domain it was received on, or null when it came through a mailbox. |
| `mailbox_id` | `string \| null` | - | The mailbox it was synced from, or null when it came in on a domain. |
| `from` | `string` | - | Sender address. |
| `to` | `string[]` | - | Recipients on the envelope. |
| `subject` | `string` | - | The subject line. |
| `snippet` | `string` | - | A short preview of the body. |
| `category` | `string \| null` | - | human, out_of_office, auto_reply, bounce, verification, transactional, notification, marketing, or spam. |
| `verdicts?` | `object` | - | Sender authentication results: spam, spf, dkim, dmarc. |
| `attachments?` | `object[]` | - | id, filename, content_type, size, preparation — metadata only. |

```json
{
  "id": "evt_...",
  "type": "email.received",
  "created_at": "2026-09-01T10:31:04Z",
  "space_id": null,
  "data": {
    "email_id": "eml_...",
    "thread_id": "thr_...",
    "domain_id": "dom_...",
    "mailbox_id": null,
    "from": "grace@example.com",
    "to": ["support@yourapp.com"],
    "subject": "Re: Quick question",
    "snippet": "Thursday at 10 works for me…",
    "category": "human",
    "verdicts": { "spam": "PASS", "spf": "PASS", "dkim": "PASS", "dmarc": "PASS" },
    "attachments": []
  }
}
```

## Handling it

The payload carries a snippet, not the body — fetch the email when you need the full content.

```ts
if (event.type === "email.received" && event.data.category === "human") {
  const email = await aiinbx.emails.retrieve(event.data.email_id)
  await queueReply(event.data.thread_id, email.text)
}
```

:::tip
Gate on `category === "human"` before letting an agent reply. Without it, an out-of-office autoresponder and your bot will happily talk to each other.
:::

**[Receiving](/guides/receiving)**

Getting mail to arrive in the first place — a domain's MX record, or a connected mailbox.
