> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Route inbound support mail to the right team and ticket

> Compose the pure-inbound shared-mailbox pattern — receive support@ on Orbit, match a route, branch a Flow on subject keywords, look up the sender in your CRM, open a ticket in the right inbox queue, ack per route, and notify Slack when a critical account writes in.

# Route inbound support mail to the right team and ticket

The [first-email-loop guide](/guides/first-email-loop) closes an outbound-then-inbound
acknowledgement loop with a Flow that replies and then hands the thread to an agent.
That is the loop a transactional send needs. This guide covers the pattern a support
mailbox actually runs every day: a customer writes *in* to a shared address such as
`support@aliceco.com`, Orbit matches the route, a Flow branches on the subject, the
sender is looked up in your CRM, a ticket opens in the right inbox queue, a per-route
acknowledgement goes back out, and a Slack notification fires when a critical account
is the one writing in. No outbound campaign is involved — this is inbound-only routing.

**You will:**

1. [See the loop you are about to build](#1-what-you-build)
2. [Match an inbound rule for the shared mailbox](#2-match-an-inbound-rule)
3. [Branch on subject inside a Flow](#3-branch-on-subject)
4. [Look up the sender in your CRM](#4-look-up-the-sender-in-your-crm)
5. [Open a ticket in the right queue](#5-open-a-ticket-from-the-route)
6. [Send a per-route auto-reply](#6-auto-reply-per-route)
7. [Notify Slack when a critical customer writes in](#7-notify-slack-on-critical-sender)
8. [Operate the routes and read failures](#8-operate)

## Prerequisites

* An API key from **Settings → API Keys** with the **owner** or **admin** role —
  inbound route management and inbox routing rules both require it.
* A domain pointed at Orbit's ingress for inbound parse (the MX steps are in
  [Inbound email and SMS routing](/guides/inbound-email-parse)). The examples use
  `inbound.aliceco.com`.
* A CRM integration connected under **Settings → Integrations** — the
  [HubSpot + Salesforce integration guide](/guides/hubspot-salesforce-integration) covers
  the connect and field-mapping steps.
* A Slack webhook URL (or any HTTPS endpoint) for the critical-customer notification.

## 1. What you build

One inbound pipeline, branching by intent:

```
customer writes support@inbound.aliceco.com
        │
        ▼
 ┌──────────────────────────────────────────────┐
 │  inbound route matches recipientPattern     │
 └──────────────────────────────────────────────┘
        │
        ▼
 ┌──────────────────────────────────────────────┐
 │  Flow triggered on message.received (email)  │
 │  branch node reads subject + CRM segment     │
 └──────────────────────────────────────────────┘
        │                    │
   billing keywords     tech / general
        │                    │
        ▼                    ▼
  billing queue        support queue
  (inbox ticket)       (inbox ticket)
        │                    │
        ▼                    ▼
  per-route ack        per-route ack
        │
        ▼
  if CRM says "critical" → Slack notify
```

The route match, the Flow branch, and the inbox ticket are three surfaces that
run in parallel in Orbit — the route forwards the parsed payload to your webhook,
the Flow reacts to the same `message.received` event, and a ticket opens in the
inbox from the inbound conversation. This guide composes them into one
shared-mailbox-with-routing-rules setup. Where the first-email-loop guide pairs a
Flow ack-reply with an outbound send, this guide pairs the Flow with **inbox routing
rules** and a **ticket** so the right team owns the work.

## 2. Match an inbound rule

Register one route per shared address so each mailbox is matchable on its own
`recipientPattern`. The matching rules and the full normalized payload are in
[Inbound email and SMS routing](/guides/inbound-email-parse); the shape that
matters here is the pattern.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/email/inbound-routes \
  -H "X-API-Key: dv_live_sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "recipientPattern": "support@inbound.aliceco.com",
    "destinationUrl": "https://api.aliceco.com/hooks/orbit-inbound",
    "includeRawMime": false,
    "description": "Support mailbox → branch Flow + ticket"
  }'
```

The `201` carries the route's `signingSecret` exactly once — store it now, because
list and get responses mask it. Repeat the call for each shared address so the Flow
can branch on the matched recipient rather than parsing the `To` header itself:

| Mailbox | `recipientPattern` | Routes to |
| - | - | - |
| `support@inbound.aliceco.com` | `support@inbound.aliceco.com` | support queue |
| `billing@inbound.aliceco.com` | `billing@inbound.aliceco.com` | billing queue |
| `help@inbound.aliceco.com` | `help@inbound.aliceco.com` | triage queue |

A catch-all (`*@inbound.aliceco.com`) is an alternative when every address funnels
into one triage queue and the Flow does the splitting — but one route per address
keeps the dashboard and the `failureCount` per mailbox readable, which matters in
[step 8](#8-operate). Registering the same `recipientPattern` twice returns
`409 CONFLICT`; update the existing route with `PATCH` instead.

## 3. Branch on subject

The Flow reacts to the inbound `message.received` event, scoped to `channel: "email"`
so it never answers an SMS or WhatsApp inbound. A branch node inspects the subject
and routes billing keywords (`invoice`, `payment`, `refund`, `charge`) to the billing
path and technical keywords (`error`, `bug`, `outage`, `api`) to the support path.
General mail falls through to triage.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/flows \
  -H "X-API-Key: dv_live_sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Support mailbox — branch by subject",
    "trigger_type": "event",
    "definition": {
      "trigger": {
        "type": "event",
        "event": "message.received",
        "filters": { "channel": "email" }
      },
      "nodes": [
        {
          "id": "classify",
          "type": "branch",
          "data": {
            "branches": [
              {
                "id": "billing",
                "when": "subject matches /invoice|payment|refund|charge/i",
                "goto": "billing-ticket"
              },
              {
                "id": "tech",
                "when": "subject matches /error|bug|outage|api/i",
                "goto": "support-ticket"
              }
            ],
            "default": "triage-ticket"
          }
        },
        {
          "id": "billing-ticket",
          "type": "setTicketFields",
          "data": { "queue": "billing", "priority": "normal", "tag": "from-billing-route" }
        },
        {
          "id": "support-ticket",
          "type": "setTicketFields",
          "data": { "queue": "support", "priority": "normal", "tag": "from-support-route" }
        },
        {
          "id": "triage-ticket",
          "type": "setTicketFields",
          "data": { "queue": "triage", "priority": "low", "tag": "from-help-route" }
        }
      ],
      "edges": [
        { "from": "classify", "to": "billing-ticket", "label": "billing" },
        { "from": "classify", "to": "support-ticket", "label": "tech" },
        { "from": "classify", "to": "triage-ticket", "label": "default" }
      ]
    }
  }'
```

The `channel: "email"` filter is the guard that keeps the branch from reacting to
every inbound SMS. The [Flows overview](/flows/overview) covers the branch node,
delays, and richer orchestration; the shape above is the minimum that splits one
shared mailbox into three queues by subject.

## 4. Look up the sender in your CRM

Before the ticket opens, enrich it with the sender's CRM record so the agent sees
account tier, open deals, and contract status on the ticket instead of pasting the
email into Salesforce by hand. The [HubSpot + Salesforce integration
guide](/guides/hubspot-salesforce-integration) covers connecting the integration and
mapping fields; once it is connected, call the CRM lookup tool from a Flow node.

Add a lookup node before the branch so every queue benefits from the same enrichment:

```json theme={null}
{
  "id": "crm-lookup",
  "type": "tool",
  "data": {
    "tool": "crm_lookup",
    "inputs": {
      "email": "{{from}}",
      "integration": "salesforce"
    },
    "outputKey": "crmContact"
  }
}
```

The node writes the CRM record into `crmContact` on the execution context, and
downstream nodes read it as `{{crmContact.tier}}`, `{{crmContact.accountName}}`,
or `{{crmContact.owner}}`. Wire the edge `crm-lookup → classify` so the branch
runs after the lookup. A sender with no CRM match sets `crmContact` to `null`; the
branch treats `null` as a general lead, so an unknown sender still lands in triage
and still gets an ack — the lookup enriches, it does not gate.

## 5. Open a ticket from the route

Every matched inbound email opens (or threads onto) an inbox **ticket** in parallel
with the webhook forward — that is the same pipeline described in
[Inbound email and SMS routing](/guides/inbound-email-parse) and operated in
[Operate the Inbox Tickets queue](/guides/inbox-tickets-workflow). The Flow's
`setTicketFields` nodes in the previous step set the queue and the source-route tag;
the [routing rules](/inbox/routing-rules) surface in the inbox is where those
assignments become live.

Open **Inbox → Settings → Routing** and create one routing rule per queue so the
tag the Flow set maps to the right team:

1. Name the rule for the intent — "Billing route → billing team".
2. Set the condition **tag is `from-billing-route`** and the action **assign to
   billing team** (or a round-robin pool over the billing roster).
3. Repeat for `from-support-route` → support team and `from-help-route` → triage.
4. Run the **dry-run** against a sample inbound email, confirm the rule matched and
   the assignment fired, then save.

Routing rules are evaluated in priority order (lowest first) and the first enabled
rule whose conditions all match wins — so set the billing and support rules at a
lower priority than the triage fallback. Every control here is tenant-owned: the
rules live in your workspace, take effect on the next inbound conversation, and
govern inbound assignment only. They never change how outbound messages are placed.

The ticket that opens carries the subject as its title, the parsed body as its
transcript, the source-route tag from the Flow, and the CRM enrichment from the
lookup node — so an agent opening the ticket sees the mailbox it came in on, the
queue it belongs to, and the customer's account tier without leaving the inbox.

## 6. Auto-reply per route

Inbound email route rules have no reply path themselves — a Flow is the designated
resolve, as stated in [Inbound email and SMS routing](/guides/inbound-email-parse).
Send a different acknowledgement per route so a billing email does not promise a
tech answer and vice versa. Add a `sendEmail` node after each `setTicketFields`
node, keyed off the same branch:

```json theme={null}
{
  "id": "billing-ack",
  "type": "sendEmail",
  "data": {
    "to": "{{from}}",
    "subject": "Re: {{subject}}",
    "body": "We received your billing message — ticket {{ticket_id}} is open in the billing queue. An agent will reply within one business day."
  }
}
```

Replace the copy for the `support-ack` node with support language and the
`triage-ack` node with general language. Keep the ack short and factual — it
confirms the ticket opened and names the queue, so the customer knows their mail
landed and where it went. The `{{ticket_id}}` variable resolves to the ticket the
inbox pipeline opened from the same inbound message, so the ack references the
exact case the agent will work.

## 7. Notify Slack on critical sender

When the CRM lookup in [step 4](#4-look-up-the-sender-in-your-crm) returns a
high-value tier, fan a notification to a Slack channel (or any HTTPS webhook) so an
account owner sees the inbound mail in real time, not when the queue rotates to it.
Add a branch after the CRM lookup that fires a notify node only when the tier
matches:

```json theme={null}
{
  "id": "critical-check",
  "type": "branch",
  "data": {
    "branches": [
      {
        "id": "critical",
        "when": "crmContact.tier == 'enterprise'",
        "goto": "slack-notify"
      }
    ],
    "default": "classify"
  }
}
```

```json theme={null}
{
  "id": "slack-notify",
  "type": "tool",
  "data": {
    "tool": "notify",
    "inputs": {
      "channel": "slack",
      "webhookUrl": "https://hooks.slack.com/services/...",
      "message": "Critical account {{crmContact.accountName}} wrote in to {{matchedRecipient}} — ticket {{ticket_id}} opened."
    }
  }
}
```

For a time-critical escalation that must not stop at Slack — a pager, an SMS to the
on-call — compose it as a [cascade (waterfall) chain](/guides/notify-cascade-failover)
so the notification tries Slack, then SMS, then a phone call in order, advancing only
when the active hop reports undelivered inside the freshness window. That keeps the
critical-customer alert from depending on a single channel being green.

## 8. Operate

Once the routes are live, the two surfaces to read are the inbound route list and
the inbound webhook debug log.

List the routes and read each one's `failureCount` and `lastFailureAt`:

```bash theme={null}
curl https://api.orbit.devotel.io/api/v1/email/inbound-routes \
  -H "X-API-Key: dv_live_sk_YOUR_KEY"
```

```json theme={null}
{
  "data": [
    {
      "id": "einr_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
      "recipientPattern": "support@inbound.aliceco.com",
      "destinationUrl": "https://api.aliceco.com/hooks/orbit-inbound",
      "active": true,
      "failureCount": 0,
      "lastFailureAt": null
    }
  ]
}
```

A rising `failureCount` with a recent `lastFailureAt` means the route matched but
the forward to your `destinationUrl` did not return `2xx` — the match still opened
an inbox ticket, but your webhook receiver is erroring. An empty `lastFailureAt`
with no ticket opening means the route never matched in the first place; re-check
the `recipientPattern` against the SMTP envelope recipient (matched first, then the
`To` and `Cc` headers), as laid out in
[Troubleshooting: inbound email never routes](/guides/inbound-email-troubleshooting).

The inbound webhook debug log under **Developer → Webhooks** shows each forward,
its response status, and the signed payload — use it to confirm the Flow fired, the
CRM lookup resolved, and the Slack notify sent. If the Flow branch misrouted a
billing email into the support queue, the fix is the branch's `when` expression or
the routing rule's tag condition, not the inbound route — the route matched
correctly and the ticket opened; the *assignment* is what to adjust.

## Next steps

* [Close the email loop: inbound parse, Flow reply, ticket](/guides/first-email-loop) —
  the outbound-then-inbound ack loop this guide branches from
* [Inbound email and SMS routing](/guides/inbound-email-parse) — the route matching
  rules and the normalized inbound payload
* [Inbox routing rules](/inbox/routing-rules) — the rule surface that assigns the
  ticket to the right team
* [Operate the Inbox Tickets queue](/guides/inbox-tickets-workflow) — the helpdesk
  queue the tickets land in
* [HubSpot + Salesforce integration](/guides/hubspot-salesforce-integration) —
  connect the CRM the lookup node reads from
* [Cascade (waterfall) fallback chains](/guides/notify-cascade-failover) —
  multi-hop escalation for the critical-customer notify
* [Flows overview](/flows/overview) — the branch, tool, and send nodes used here
* [Troubleshooting: inbound email never routes](/guides/inbound-email-troubleshooting) —
  diagnose a route that never fires


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.