Skip to content
couryo

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.

View as Markdown

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...).

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 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_...).

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:

Terminal
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; with step_id, to a specific email. See Sequences.
  • 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: the agent reads replies, support requests and complaints, with the triage.