Triage & Route an Inbound Support Ticket
Classify any inbound support message by intent, urgency, and product area, draft a first reply, and route it to the right queue — with a confidence gate that escalates to a human when unsure.
supporttriageroutingclassificationcustomer-service
# Triage & Route an Inbound Support Ticket
## Purpose
Turn a raw inbound support message into a structured, actionable ticket: a classification (intent, urgency, product area), a drafted first response, and a routing decision (queue + suggested owner). The goal is consistent, fast first-touch handling with a clear escape hatch to a human when the model is not confident.
## When to use
Use this when a new support message arrives (email, chat, web form, in-app) and you need to decide what it is, how fast it must be handled, who should own it, and what the customer should hear first. Use it for first-touch triage — not for resolving complex issues end to end.
## Inputs
- `message` (required): the raw customer message text.
- `channel` (optional): where it came from (`email`, `chat`, `web_form`, `social`, `phone_transcript`).
- `customer` (optional): known context — name, plan/tier, account ID, language.
- `history` (optional): prior tickets or recent conversation for context.
- `taxonomy_overrides` (optional): org-specific intents, queues, or SLA tiers that replace the defaults below.
Never invent customer data (account IDs, order numbers, plan tier). If a field is absent, treat it as unknown.
## Steps
1. **Normalize.** Strip signatures, quoted reply chains, and tracking footers. Detect language; if not English (or the org's default), note it and continue in the customer's language.
2. **Classify intent** into exactly one primary intent (multiple allowed only as `secondary_intents`):
- `billing` — charges, invoices, refunds, plan changes
- `bug` — something broken or erroring that should work
- `how_to` — usage question, no defect
- `feature_request` — wants something that does not exist
- `account_access` — login, password, SSO, lockout, permissions
- `outage` — broad service unavailability (often multiple reporters)
- `security` — vulnerability report, suspected breach, abuse
- `complaint` — dissatisfaction/escalation without a specific defect
- `sales` — pre-sale, upgrade, procurement
- `spam` — irrelevant/automated/malicious noise
- `other` — none of the above (always pair with `needs_human: true`)
3. **Assess urgency** using observable signals, not tone alone:
- `P1_critical` — production down, data loss, active security incident, payment fully blocked, or contractual SLA breach.
- `P2_high` — major feature unusable, no workaround, or a paying/enterprise customer blocked.
- `P3_normal` — degraded or inconvenient but a workaround exists.
- `P4_low` — question, cosmetic issue, or feature request.
Upgrade one level if `customer.tier` is enterprise/VIP and they are blocked. Never downgrade `security` or `outage` below P2.
4. **Identify product area** from the org's component list (or infer a coarse area: `web_app`, `mobile`, `api`, `billing_system`, `integrations`, `infra`, `docs`, `unknown`). Use `unknown` rather than guessing.
5. **Route.** Map intent + product area + urgency to a queue and a suggested owner/team. Defaults: `billing`→Billing, `security`→Security on-call, `outage`/P1→Incident response, `sales`→Sales, everything else→the product-area engineering/support queue. Suggested owner is a hint, not an assignment.
6. **Draft a first response.** Acknowledge, confirm what you understood, state the next step or ask the single most useful clarifying question, and set a realistic expectation. Match the customer's language and a professional, empathetic tone. Do not promise fixes, refunds, timelines, or compensation you cannot guarantee. Do not paste internal notes.
7. **Confidence gate.** Produce `confidence` (0–1) for the classification. If `confidence < 0.6`, intent is `other`/`security`/`outage`, the message is ambiguous, or it implies legal/financial/safety stakes, set `needs_human: true` and keep the draft as a suggestion only.
## Output
Return a single JSON object:
```json
{
"intent": "bug",
"secondary_intents": ["account_access"],
"urgency": "P2_high",
"product_area": "api",
"language": "en",
"confidence": 0.82,
"needs_human": false,
"routing": { "queue": "api-support", "suggested_owner": "API on-call" },
"summary": "Enterprise customer's API requests started returning 500s ~2h ago; no workaround found.",
"suggested_reply": "Hi Dana, thanks for flagging this. I see your API calls began returning 500 errors about two hours ago. I've routed this to our API on-call team as a high-priority issue and we're investigating now. To help us narrow it down, could you share one affected request ID or endpoint? I'll follow up as soon as we have an update.",
"signals": ["mentions 500 errors", "says 'production is affected'", "enterprise tier"]
}
```
List `signals` as short verbatim-or-paraphrased cues from the message that justify the urgency and intent — this makes the decision auditable.
## Guardrails & notes
- **No hallucination / cite the signal.** Every urgency or intent call must trace to a `signal` from the actual message or provided context. If you cannot, lower confidence and set `needs_human`.
- **Confidence is a real gate, not decoration.** When unsure, escalate rather than guessing — a misrouted P1 outage is far costlier than a human glance.
- **PII handling.** Do not echo full card numbers, passwords, API keys, or government IDs back in the reply or summary; mask them (e.g. `****1234`). Never store secrets the customer pastes.
- **Idempotency.** Triaging the same message twice must yield the same routing. Key any downstream action on a stable ticket/message ID so retries don't create duplicate tickets or double-send the reply.
- **Failure modes to avoid:** over-trusting an angry tone as P1 (rage ≠ severity); defaulting `unknown` product areas into a real engineer's queue; auto-sending a reply that promises a refund or fix.
- **Security & safety override:** anything resembling a vulnerability report, breach, or threat goes to the security/incident path regardless of other signals, with `needs_human: true`.
- **No secrets in examples or config.** Use placeholders like `<ACCOUNT_ID>` / `****1234`; never include real keys, tokens, or customer data.
## Example
**Input:** `channel: email`, message: "This is the THIRD time I've been charged twice this month. Fix it or I'm canceling. Account #A-50912."
**Output (abridged):** intent `billing`, secondary `complaint`, urgency `P2_high` (paying customer, billing blocked, repeat issue), product_area `billing_system`, confidence `0.88`, needs_human `false`, queue `billing`. Reply acknowledges the double charge, confirms account `A-50912`, says it's been routed to Billing for review and a refund will be assessed (without promising the refund outright), and asks for the two charge dates. `signals`: ["charged twice", "third time this month", "threatening to cancel"].Use this skill
Install creates a private, read-only copy in your own registry. Fork creates your own public, editable copy that permanently credits this source (a fork can never be made private). Both run from your agent with an API key, or via the skill_install / skill_fork MCP tools.
curl -X POST https://agentprizm.com/api/v1/agent/marketplace/install \
-H "Authorization: Bearer ap_your_key" \
-H "Content-Type: application/json" \
-d '{"sourceSkillId":"6a3d5f25ff1e38ac55db55e6"}'curl -X POST https://agentprizm.com/api/v1/agent/marketplace/fork \
-H "Authorization: Bearer ap_your_key" \
-H "Content-Type: application/json" \
-d '{"sourceSkillId":"6a3d5f25ff1e38ac55db55e6"}'Ship agents that remember.
Six lines of code. Confidence scores, validity windows, and audit trails included. Free until your agents ship.