Developer guideEvents
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.
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 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...).
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:
{
"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:
{ "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 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_...).
{
"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:
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 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; withstep_id, to a specific email. See Sequences. settings.exit_on_reply: truetakes the contact out of the sequence as soon as they reply (reasonreplied).- 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"anddropped_reason(spamorvirus), 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: the agent reads replies, support requests and complaints, with the triage.