Developer Handover Pack
Everything needed to build the automation from scratch: current state, full agent specs, data flow, and the build stack.
Developer Handover Pack
Support Ticket Triage & Routing
[YourCompany.com] · Customer Support Department · Prepared by FullSpec · [Today's Date]
This document gives the FullSpec build team everything needed to configure, connect, and test the Support Ticket Triage & Routing automation from first credential to go-live. It covers the full current-state step map with bottlenecks identified, precise agent specifications with IO contracts, an end-to-end data flow trace, and the recommended build stack. The process owner's role during build is to supply documented routing rules and a labelled ticket export; everything else is handled by FullSpec.
01Process snapshot
02Agent specifications
Reads the subject line and full body of each new Zendesk ticket, cross-references the customer's CRM record fetched from HubSpot, and applies a trained classification schema to output a category label and a priority score. Handles ambiguous language and multi-issue tickets by selecting the dominant primary intent. Where confidence falls below the defined threshold, the agent flags the ticket for human review rather than writing a speculative classification. Output fields are written directly back to the ticket record in Zendesk before the Routing and Notification Agent fires.
// Input zendesk.ticket.id : string // Zendesk ticket ID zendesk.ticket.subject : string // Ticket subject line zendesk.ticket.body : string // Full plain-text ticket body zendesk.ticket.channel : string // email | chat | web_form zendesk.requester.email : string // Customer email address hubspot.contact.plan_tier : string // e.g. Free | Starter | Pro | Enterprise hubspot.contact.account_status : string // active | churned | trial hubspot.contact.open_tickets : integer // Count of currently open tickets hubspot.contact.recent_ticket_ids : array<string> // Output classification.category : string // billing | technical | account_access | general classification.priority : string // low | normal | high | urgent classification.confidence : float // 0.0 to 1.0 classification.primary_intent : string // One-sentence summary of dominant issue classification.flag_for_review : boolean // true if confidence < 0.70 // On low confidence (flag_for_review = true) zendesk.ticket.tag : string // 'triage_review_required' zendesk.ticket.assignee : string // Team Lead queue (manual exception path)
- Zendesk plan tier required: Suite Team or above (API access to ticket field writes and webhook triggers). Confirm this with the process owner before build starts.
- HubSpot tier required: Free CRM is sufficient for contact lookup by email via the Contacts API. If the customer email is not found in HubSpot, the agent must proceed with ticket data only and log a 'hubspot_record_not_found' flag on the ticket rather than halting.
- Classification schema must be finalised before training: the four categories (billing, technical, account_access, general) are assumed from the template. Any additional categories require a revised schema and retraining.
- Training dataset: a minimum of 100 to 200 historically labelled tickets with confirmed category and priority values must be provided by the process owner. Store labelled set in a version-controlled document (Notion or exported CSV) before the classification agent build begins.
- Confidence threshold is set to 0.70 by default. Tickets scoring below this value must not receive an automated category or priority write. They are tagged 'triage_review_required' and assigned to the Team Lead queue. The threshold must be configurable without a code deploy.
- Dedupe behaviour: if the webhook fires more than once for the same ticket ID (Zendesk retry on timeout), the agent must check whether 'classification.category' is already populated. If the field is not empty, skip processing and log a 'duplicate_webhook_skipped' event.
- Field naming in Zendesk: confirm the exact API field names for category tag and priority before writing. Use 'tags' array for category and the built-in 'priority' enum field (low, normal, high, urgent) rather than custom fields unless the process owner confirms custom fields are in use.
- Confirm with the process owner before build: which ticket types, if any, must never be auto-classified and must always enter the manual review queue regardless of confidence score.
Fires immediately after the Ticket Classification Agent has written category and priority to the Zendesk ticket record. Applies a routing rule table to match the ticket's category and priority combination to the correct agent or specialist queue, updates the Zendesk assignee field, adds a pre-formatted internal context note, sends a Slack direct message to the assigned agent, and dispatches a personalised acknowledgement email to the customer via Gmail. This agent handles all post-classification actions in sequence; if any downstream action fails, the failure is logged and a fallback Slack alert is sent to the Team Lead.
// Input zendesk.ticket.id : string zendesk.ticket.subject : string zendesk.requester.email : string zendesk.requester.name : string classification.category : string // billing | technical | account_access | general classification.priority : string // low | normal | high | urgent classification.primary_intent : string hubspot.contact.plan_tier : string hubspot.contact.open_tickets : integer routing_rules_table : object // Loaded from Notion or config store at runtime // Output zendesk.ticket.assignee_id : string // Zendesk agent user ID zendesk.internal_note : string // Formatted context note body slack.dm.recipient : string // Slack user ID of assigned agent slack.dm.message : string // Ticket number, category, priority, direct URL gmail.to : string // Customer email address gmail.subject : string // 'We received your request [Ticket #XXXXX]' gmail.body : string // Personalised acknowledgement with SLA window // On routing or notification failure error_log.event : string // 'routing_failed' | 'slack_failed' | 'gmail_failed' error_log.ticket_id : string error_log.timestamp : datetime slack.fallback_alert.channel : string // #support-ops or Team Lead DM
- Routing rules table must exist before this agent can be built. The table maps category-priority combinations to specific Zendesk agent user IDs. If the process owner has not yet documented routing rules, a mapping session is required and build of this agent must be blocked until the table is complete. Store the rules in Notion and load them at runtime via the Notion API or export them to a static config file in the credential store.
- Slack integration requires the automation platform's bot to be installed in the workspace with the 'chat:write' and 'users:read' OAuth scopes. Confirm Slack user IDs for every agent in the routing table before build. Do not use display names as identifiers.
- Gmail send must use a shared support mailbox (e.g. support@[YourCompany.com]) authorised via OAuth 2.0. Do not use a personal agent Gmail account. Confirm the mailbox address and OAuth refresh token before build.
- Acknowledgement email SLA window is priority-driven: urgent = 1 hour, high = 4 hours, normal = 1 business day, low = 2 business days. These values must be confirmed by the process owner and stored as configurable constants, not hardcoded strings.
- Internal context note template must be version-controlled. The note should include: ticket category, priority, customer plan tier, count of open tickets, primary intent summary, and a timestamp. Do not include raw ticket body text in the note to avoid duplication.
- Fallback behaviour: if no routing rule matches a category-priority pair (e.g. a new category is added without updating the rules table), the ticket must be assigned to the Team Lead queue, tagged 'routing_rule_missing', and a Slack alert sent to #support-ops. Never leave a ticket unassigned.
- Round-robin and capacity-based routing are out of scope for the Standard build. If the process owner requires workload-balanced assignment rather than category-based assignment, this must be raised as a scope change before the routing agent build begins.
03End-to-end data flow
// ─────────────────────────────────────────────────────────────────
// TRIGGER: New ticket created in Zendesk (any inbound channel)
// ─────────────────────────────────────────────────────────────────
WEBHOOK_PAYLOAD {
zendesk.ticket.id : '98432'
zendesk.ticket.subject : 'Cannot access my account after password reset'
zendesk.ticket.body : '<full plain-text body>'
zendesk.ticket.channel : 'email'
zendesk.ticket.created_at : '2025-06-03T09:14:22Z'
zendesk.requester.email : 'customer@example.com'
zendesk.requester.name : 'Alex Rivera'
}
// ─────────────────────────────────────────────────────────────────
// STEP 1: HubSpot contact lookup (by requester email)
// ─────────────────────────────────────────────────────────────────
HubSpot.Contacts.search({
filterGroups: [{ filters: [{ propertyName: 'email',
operator: 'EQ',
value: zendesk.requester.email }] }]
})
RETURNS {
hubspot.contact.id : 'hs-1029384'
hubspot.contact.plan_tier : 'Pro'
hubspot.contact.account_status : 'active'
hubspot.contact.open_tickets : 1
hubspot.contact.recent_ticket_ids : ['98100', '97881']
}
// If no HubSpot record: set hubspot.contact.plan_tier = 'unknown',
// log 'hubspot_record_not_found', continue to classification.
// ─────────────────────────────────────────────────────────────────
// AGENT HANDOFF 1: Ticket Classification Agent receives merged payload
// ─────────────────────────────────────────────────────────────────
CLASSIFICATION_INPUT {
ticket_id : zendesk.ticket.id
subject : zendesk.ticket.subject
body : zendesk.ticket.body
channel : zendesk.ticket.channel
requester_email : zendesk.requester.email
plan_tier : hubspot.contact.plan_tier
account_status : hubspot.contact.account_status
open_tickets : hubspot.contact.open_tickets
}
// Classification model evaluates subject + body + account context
CLASSIFICATION_OUTPUT {
classification.category : 'account_access'
classification.priority : 'high'
classification.confidence : 0.91
classification.primary_intent : 'Customer locked out after password reset'
classification.flag_for_review: false
}
// ─────────────────────────────────────────────────────────────────
// CONFIDENCE GATE
// ─────────────────────────────────────────────────────────────────
IF classification.confidence < 0.70 THEN
zendesk.PUT /tickets/98432 {
tags: ['triage_review_required'],
assignee_id: TEAM_LEAD_USER_ID
}
LOG 'low_confidence_routed_to_human' -> error_log table
HALT // Routing and Notification Agent does not fire
END IF
// ─────────────────────────────────────────────────────────────────
// STEP 2: Write classification fields back to Zendesk ticket
// ─────────────────────────────────────────────────────────────────
Zendesk.PUT /api/v2/tickets/98432 {
priority : 'high'
tags : ['account_access']
custom_fields: [
{ id: CATEGORY_FIELD_ID, value: 'account_access' },
{ id: CONFIDENCE_FIELD_ID, value: 0.91 }
]
}
// Dedupe check: if category field already populated, skip and log
// 'duplicate_webhook_skipped' then HALT
// ─────────────────────────────────────────────────────────────────
// AGENT HANDOFF 2: Routing and Notification Agent receives output
// ─────────────────────────────────────────────────────────────────
ROUTING_INPUT {
ticket_id : '98432'
subject : zendesk.ticket.subject
requester_email : 'customer@example.com'
requester_name : 'Alex Rivera'
category : 'account_access'
priority : 'high'
primary_intent : 'Customer locked out after password reset'
plan_tier : 'Pro'
open_tickets : 1
routing_rules : { account_access+high: AGENT_USER_ID_42 }
}
// ─────────────────────────────────────────────────────────────────
// STEP 3: Assign ticket in Zendesk
// ─────────────────────────────────────────────────────────────────
Zendesk.PUT /api/v2/tickets/98432 {
assignee_id: 'AGENT_USER_ID_42'
}
// ─────────────────────────────────────────────────────────────────
// STEP 4: Add internal context note to ticket
// ─────────────────────────────────────────────────────────────────
Zendesk.POST /api/v2/tickets/98432/comments {
body : '[AUTO] Category: Account Access | Priority: High\n
Plan Tier: Pro | Open tickets: 1\n
Intent: Customer locked out after password reset\n
Classified at: 2025-06-03T09:14:35Z'
public : false
}
// ─────────────────────────────────────────────────────────────────
// STEP 5: Slack DM to assigned agent
// ─────────────────────────────────────────────────────────────────
Slack.POST /api/chat.postMessage {
channel : 'SLACK_USER_ID_AGENT_42'
text : ':ticket: New ticket #98432 assigned to you\n
Category: Account Access | Priority: High\n
https://[YourCompany.zendesk.com]/tickets/98432'
}
// ─────────────────────────────────────────────────────────────────
// STEP 6: Customer acknowledgement email via Gmail
// ─────────────────────────────────────────────────────────────────
Gmail.POST /gmail/v1/users/me/messages/send {
to : 'customer@example.com'
subject : 'We received your request [Ticket #98432]'
body : 'Hi Alex, thanks for getting in touch. We have received
your request and our team will respond within 4 hours.
Reference: #98432.'
}
// ─────────────────────────────────────────────────────────────────
// ON ANY DOWNSTREAM FAILURE (Slack or Gmail)
// ─────────────────────────────────────────────────────────────────
LOG {
error_log.event : 'slack_failed' | 'gmail_failed'
error_log.ticket_id : '98432'
error_log.timestamp : datetime
}
Slack.POST fallback_alert -> #support-ops | Team Lead DM
// ─────────────────────────────────────────────────────────────────
// END OF AUTOMATED CYCLE
// Total automated elapsed time: target < 90 seconds from webhook
// ─────────────────────────────────────────────────────────────────04Recommended build stack
More documents for this process
Every document generated for Support Ticket Triage & Routing.