# Receiving email (inbound)

> How to receive email in Couryo by webhook. A receiving domain with an MX, the email.received event, the dashboard inbox, AI triage (reply, support, complaint, unsubscribe, out of office, spam) and the "replied" condition of sequences.

Source: https://couryo.com/en/docs/inbound

Couryo receives the emails that arrive on a **receiving domain** of yours, like `reply.yourcompany.com`, and hands each one to your system through the `email.received` webhook. Replies to your emails, support requests and complaints arrive already linked to the email you sent and, if you want, sorted by AI.

Available on the **Pro, Scale and Enterprise** plans. On Free, the routes answer `403` with the [`inbound_not_available`](https://couryo.com/en/docs/errors.md#inbound_not_available) code and `upgrade_options`.

## 1. Add the receiving domain

Use a **subdomain of a domain already verified** in the project (recommended) or the verified domain itself. With a subdomain, the MX of your main domain keeps going to your mailboxes (Google Workspace, Microsoft 365...).

```bash title="Add reply.example.com"
curl https://api.couryo.com/v1/inbound-domains \
  -H "Authorization: Bearer $COURYO_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "reply.example.com" }'
```

The response carries the record to publish in your DNS:

```json
{
  "id": "ind_8k2m4q6s0w1y3a5c",
  "name": "reply.example.com",
  "status": "pending",
  "records": [
    { "purpose": "inbound", "type": "MX", "name": "reply.example.com", "value": "inbound-smtp.couryo.com", "priority": 10, "status": "pending" }
  ],
  "created_at": "2026-10-08T18:30:00Z"
}
```

Create the **MX** with priority **10** on the given name and click **Check now** in the dashboard (under **Inbox**) or call `POST /v1/inbound-domains/{id}/verify`. The status turns `verified` when the MX points to Couryo; we check on our own every 10 minutes for the first 72 hours and then once a day.

## 2. Ask for replies on the receiving domain

For replies to reach Couryo, use an address of the receiving domain as the `reply_to` of your emails:

```json
{ "from": "Shop <orders@example.com>", "reply_to": "help@reply.example.com", "to": "maria@customer.com", "subject": "Your order shipped", "html": "..." }
```

Any address of the receiving domain works: `help@`, `sales@`, `order-4821@`...

## 3. Receive through the webhook

Create a [webhook](https://couryo.com/en/docs/webhooks.md) with the `email.received` event. The call has the same signature (Standard Webhooks) and the same retries as the other events; the `webhook-id` is the id of the received email (`inb_...`).

```json title="Body of email.received"
{
  "type": "email.received",
  "timestamp": "2026-10-08T18:31:04Z",
  "data": {
    "id": "inb_3f9k1m5q7s2u4w6y",
    "project_id": "prj_...",
    "domain": "reply.example.com",
    "from": "maria@customer.com",
    "from_name": "Maria Souza",
    "to": ["help@reply.example.com"],
    "cc": [],
    "reply_to": [],
    "subject": "Re: Your order shipped",
    "text": "Hi! It arrived, thank you.",
    "html": "<p>Hi! It arrived, thank you.</p>",
    "headers": { "message_id": "CAF...@mail.gmail.com", "in_reply_to": "msg_7h2kq9w4x1abcdef@example.com", "references": ["msg_7h2kq9w4x1abcdef@example.com"], "date": "2026-10-08T18:31:00Z", "auto_submitted": null, "list_id": null },
    "attachments": [{ "filename": "photo.jpg", "content_type": "image/jpeg", "size": 48213, "content_id": null, "inline": false }],
    "size": 71234,
    "verdicts": { "spam": "pass", "virus": "pass", "spf": "pass", "dkim": "pass", "dmarc": "pass" },
    "auto_reply": false,
    "in_reply_to_email_id": "msg_7h2kq9w4x1abcdef",
    "ai_status": "done",
    "ai": { "category": "reply", "summary": "Customer confirms the order arrived.", "priority": "normal" },
    "actions": [],
    "received_at": "2026-10-08T18:31:02Z"
  }
}
```

| Field | What it is |
|---|---|
| `in_reply_to_email_id` | the email you sent that this message answers (from the `In-Reply-To` and `References` headers); `null` when it is not a reply to a Couryo email |
| `html` | already without scripts, frames, forms and event attributes |
| `attachments` | only the data of each attachment (name, type, size); **attachment contents are not kept** |
| `verdicts` | the spam, virus, SPF, DKIM and DMARC checks made on arrival (`pass`, `fail`, `gray`, `processing_failed`) |
| `auto_reply` | automatic answer by its headers (`Auto-Submitted`, `X-Autoreply`, `Precedence`) or by an out-of-office subject |
| `ai` | the AI triage, when on (below) |
| `actions` | what Couryo did because of the message: `suppressed_marketing` (asked to unsubscribe) and `sequence_exit` (left a sequence after replying) |

## List through the API

`GET /v1/inbound` lists received emails, newest first, with cursor pagination and the `category`, `status` (`received` or `dropped`) and `q` (part of the sender or subject) filters. `GET /v1/inbound/{id}` returns one. Scope `read`.

In the dashboard, **Inbox** is the project inbox, with category filters, a safe HTML preview (external content blocked) and the receiving domains.

## AI triage

Turn it on under **Inbox > AI triage** or through the API:

```bash
curl -X PATCH https://api.couryo.com/v1/inbound-settings \
  -H "Authorization: Bearer $COURYO_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "ai": true }'
```

Each received email gets `ai.category`, a one-line summary (`ai.summary`) and the priority (`low`, `normal` or `high`). It costs **1 [AI credit](https://couryo.com/en/docs/ai.md) per email**. Without credits or if the AI fails, the email arrives the same, with `ai_status` `no_credits` or `failed` and nothing charged.

| Category | When |
|---|---|
| `reply` | a person replying, a normal conversation |
| `support` | asks for help, reports a problem or asks a question |
| `complaint` | angry customer, complaint, threatens to cancel |
| `unsubscribe` | asks to stop receiving email, in any wording |
| `out_of_office` | automatic absence or vacation answer |
| `spam` | unsolicited advertising, scam or phishing |

**Unsubscribe asked in plain words:** when the category is `unsubscribe`, the sender leaves the project's `marketing` stream right away (suppression with reason `unsubscribe`, their sequences stop) and the message gets `actions: ["suppressed_marketing"]`. Transactional emails (password, billing) keep going.

## Sequences: "replied"

- The `{ "kind": "replied" }` condition is true when the contact replied to an email of the enrollment; with `step_id`, to a specific email. See [Sequences](https://couryo.com/en/docs/sequences.md).
- `settings.exit_on_reply: true` takes the contact out of the sequence as soon as they reply (reason `replied`).
- **Out of office does not count as a reply**, nor do automatic answers and spam.

## Rules

- **Spam and viruses** flagged on arrival are dropped: the email shows with `status: "dropped"` and `dropped_reason` (`spam` or `virus`), without content and without a webhook.
- **Size:** up to 10 MB per message; above it, `dropped_reason: "too_large"`.
- **Plan:** if the account goes back to Free, incoming emails are recorded with `dropped_reason: "plan"`, without content.
- **Retention:** received emails stay as long as the plan's logs (Pro 30 days, Scale 90, Enterprise 365).
- **Privacy:** full addresses only show to your own account (API, dashboard and your MCP).

## Through MCP

`list_inbound` and `get_inbound` (scope `read`) on the [MCP server](https://couryo.com/en/docs/mcp.md): the agent reads replies, support requests and complaints, with the triage.
