Back to Complaint Escalation Workflow

Developer Handover Pack

Everything needed to build the automation from scratch: current state, full agent specs, data flow, and the build stack.

4 pagesPDF · Technical
FS-DOC-04Technical

Developer Handover Pack

Complaint Escalation Workflow

[YourCompany.com] · Customer Support Department · Prepared by FullSpec · [Today's Date]

This document gives the FullSpec build team everything needed to implement the Complaint Escalation Workflow from a confirmed process map. It covers the current-state step breakdown, full agent specifications with IO contracts, an end-to-end data flow trace, and the recommended build stack. All numbered facts, volumes, and costs come directly from the confirmed process template. Where the template does not specify a value (OAuth scopes, rate limits, field names), FullSpec has supplied realistic, internally consistent specifics based on the named tools. Do not share this document with the client team without redacting the agent IO code blocks.

01Process snapshot

Step
Name
Description
1
Complaint Received and Acknowledged
Agent reads incoming email or web form complaint and sends a generic acknowledgement reply to the customer. Time cost: 8 min/complaint.
2
Complaint Logged in Helpdesk
Agent manually creates a Zendesk ticket, copying in customer details, complaint text, and attachments. Time cost: 7 min/complaint.
3 BOTTLENECK
Severity Assessed Manually
Agent assigns a severity level by personal judgement with no scoring rubric, causing wide variation between agents. Time cost: 10 min/complaint.
4
Complaint Routed to Owner
Agent decides which team member or department should own the complaint and forwards it manually or via Slack. Time cost: 5 min/complaint.
5
Customer Record Checked in CRM
Assigned agent opens HubSpot to look up customer history, purchase records, and prior complaints before responding. Time cost: 8 min/complaint.
6 BOTTLENECK
Manager Notified for High-Severity Cases
Agent messages the support manager on Slack for high-severity cases. Step is skipped or delayed when agent is busy. Time cost: 5 min/complaint.
7
Initial Response Drafted and Sent
Assigned agent writes and sends an individual response from Gmail or Zendesk with no consistent template. Time cost: 15 min/complaint.
8
Follow-Up Reminder Set
Agent manually sets a calendar reminder or sticky note to follow up if complaint is unresolved. Time cost: 4 min/complaint.
9 BOTTLENECK
Escalation Chased if Overdue
Manager manually chases the assigned agent only when someone notices the ticket is overdue. Time cost: 10 min/complaint.
10
Resolution Logged and Ticket Closed
Agent marks ticket closed in Zendesk, notes resolution method, and sometimes adds a summary to Notion. Time cost: 6 min/complaint.
Time cost summary: Total manual time per complaint cycle is 78 minutes. At approximately 90 complaints per month (roughly 22 per week) this equates to 6 hours of manual labour lost every week and approximately 26 hours every 30 days. The automation replaces steps 1, 2, 3, 4, 5, 6, 7, 8, and 9 in full. Step 10 (resolution and close) remains with a human agent. The three bottleneck steps (3, 6, 9) account for 25 minutes per complaint and are the primary source of inconsistency and missed escalations.
Developer Handover PackPage 1 of 4
FS-DOC-04Technical

02Agent specifications

Complaint Triage Agent

The Complaint Triage Agent fires the moment a new ticket is created in Zendesk from an incoming complaint. It reads the complaint body, runs sentiment analysis and keyword matching against a configurable severity ruleset, retrieves the customer's full record from HubSpot (purchase history, lifetime value, and prior complaint count), and produces a structured triage output: a severity score (low, medium, or high), a complaint category (billing, product, delivery, conduct, or other), and the enriched customer context block. All three outputs are written directly back to the Zendesk ticket as custom fields before the Escalation and Routing Agent fires. Estimated build time: 10 hours. Complexity: Moderate.

Trigger
New ticket created event in Zendesk (ticket.created webhook, target URL set on the automation platform's inbound endpoint).
Tools
Zendesk (ticket read and field write), HubSpot (contact and deal record lookup by email address).
Replaces steps
Step 3 (manual severity assessment), Step 5 (manual HubSpot record lookup).
Estimated build
10 hours, Moderate complexity.
// Input
zendesk.ticket.id          : string   // e.g. '10042'
zendesk.ticket.subject     : string   // complaint subject line
zendesk.ticket.description : string   // full complaint body text
zendesk.ticket.requester_email : string // customer email address
zendesk.ticket.created_at  : ISO8601  // timestamp of ticket creation

// HubSpot lookup (by requester_email)
hubspot.contact.id              : string
hubspot.contact.lifecycle_stage : string  // e.g. 'customer'
hubspot.contact.num_contacted_notes : integer // prior complaint count
hubspot.contact.recent_deal_amount  : number  // last purchase value USD
hubspot.contact.hs_lead_status      : string

// Output (written to Zendesk custom fields)
triage.severity_score    : enum['low','medium','high']
triage.complaint_category: enum['billing','product','delivery','conduct','other']
triage.sentiment_label   : enum['negative','highly_negative','neutral']
triage.customer_tier     : enum['standard','valued','vip']  // derived from deal amount
triage.prior_complaints  : integer   // from HubSpot contact
triage.hubspot_contact_id: string    // appended for downstream lookups
triage.triage_timestamp  : ISO8601
  • Zendesk tier requirement: Suite Team or above is required for custom ticket fields and webhook targets. Confirm the active plan before build begins. Professional tier is needed if SLA policy objects are to be read by the agent.
  • HubSpot tier requirement: Free CRM tier is sufficient for contact GET requests. If deal pipeline data is required for customer tier scoring, a Starter tier or above must be confirmed.
  • Severity scoring ruleset: The ruleset (keywords, sentiment thresholds, tier multipliers) must be agreed in writing with the support manager before the agent is built. Default thresholds are: any 'highly_negative' sentiment with 2+ prior complaints scores high; 'highly_negative' with 0-1 prior complaints scores medium; all other combinations score low.
  • HubSpot lookup fallback: If no contact is found by email, the agent writes triage.customer_tier as 'standard' and triage.prior_complaints as 0, then continues. It does not block the workflow. The Zendesk ticket must receive a flag field 'triage.hubspot_matched: false' in this case.
  • Dedupe: Zendesk ticket IDs are globally unique. No additional dedupe is required at this layer. Duplicate complaints submitted by the same customer within 10 minutes should be detected by checking for an open ticket with the same requester_email and a created_at within a 600-second window. If found, append a note to the existing ticket rather than creating a triage output for the duplicate.
  • Custom field setup: Four Zendesk custom fields must be created before build: 'Severity Score' (dropdown), 'Complaint Category' (dropdown), 'Customer Tier' (dropdown), and 'Prior Complaints' (integer). Field IDs must be captured and added to the platform credential store before the agent node is wired.
  • Confirm before build: Severity scoring criteria signed off by Priya Sandhu (Support Manager), HubSpot API key scoped to contacts.read and crm.objects.deals.read, and Zendesk webhook target URL registered on the active subdomain.
Escalation and Routing Agent

The Escalation and Routing Agent fires after the Complaint Triage Agent has written its output fields to the Zendesk ticket. It reads the severity score and complaint category, assigns the ticket to the correct agent or team queue in Zendesk using a configurable routing matrix, sends a Slack notification to the assigned agent with a direct ticket link and complaint summary, and, for high-severity cases only, sends a separate Slack alert to the support manager. It immediately sends a personalised acknowledgement email to the customer via Gmail, selecting the correct template variant based on severity tier (low, medium, or high). It then sets a time-based escalation check: if the ticket status has not moved to 'solved' or 'pending' by the agreed deadline, the agent fires an overdue alert to the manager's Slack channel and updates the ticket priority to 'urgent' in Zendesk. Estimated build time: 14 hours. Complexity: Complex.

Trigger
Completion of Complaint Triage Agent output (internal platform event: triage fields written to Zendesk ticket, confirmed by polling triage.triage_timestamp field or a direct chained workflow step).
Tools
Zendesk (ticket assignment, priority update, status polling), Slack (agent notification, manager alert, overdue escalation), Gmail (customer acknowledgement email send).
Replaces steps
Step 1 (manual acknowledgement), Step 2 (ticket logging), Step 4 (manual routing), Step 6 (manager notification), Step 7 (initial response), Step 8 (follow-up reminder), Step 9 (overdue chase). Note: Step 2 ticket creation is handled upstream by the Zendesk trigger; this agent handles the assignment and enrichment portions that follow triage.
Estimated build
14 hours, Complex.
// Input (from Zendesk ticket fields written by Triage Agent)
zendesk.ticket.id              : string
triage.severity_score          : enum['low','medium','high']
triage.complaint_category      : enum['billing','product','delivery','conduct','other']
triage.customer_tier           : enum['standard','valued','vip']
triage.prior_complaints        : integer
triage.hubspot_contact_id      : string
zendesk.ticket.requester_email : string
zendesk.ticket.requester_name  : string
zendesk.ticket.created_at      : ISO8601

// Routing matrix lookup (stored in platform credential / config store)
routing.matrix[category][severity] -> zendesk.group_id : string
routing.matrix[category][severity] -> zendesk.assignee_id : string

// Output: Zendesk updates
zendesk.ticket.group_id    : string   // assigned group
zendesk.ticket.assignee_id : string   // assigned agent
zendesk.ticket.priority    : enum['low','normal','high','urgent']
zendesk.ticket.status      : 'open'

// Output: Slack agent notification
slack.channel   : string  // agent's direct Slack channel ID
slack.message   : string  // ticket link, severity, category, customer name

// Output: Slack manager alert (high-severity only)
slack.channel   : string  // #support-escalations or manager DM
slack.message   : string  // complaint summary, customer tier, prompt to review

// Output: Gmail acknowledgement email
gmail.to        : string  // requester_email
gmail.subject   : string  // severity-tier template subject
gmail.body_html : string  // personalised template: agent name, ETA, ticket ref

// Output: Overdue escalation (time-based, fires at deadline breach)
slack.channel          : string  // manager channel
slack.overdue_message  : string  // ticket ID, time overdue, current assignee
zendesk.ticket.priority: 'urgent'  // updated on breach
  • Routing matrix: Must be built as a configurable lookup table (JSON object in the platform credential/config store), keyed by complaint_category and severity_score. Do not hard-code agent IDs. All Zendesk group_id and assignee_id values must be retrieved via the Zendesk Groups and Users API at build time and stored in the config table.
  • Slack OAuth scopes required: chat:write, users:read, users:read.email. The bot must be invited to all target channels (#support-escalations and any agent DM threads) before go-live. Confirm channel IDs with the support manager before wiring.
  • Gmail send account: A dedicated support@ sending address must be configured with OAuth2 (scope: gmail.send). Do not use a personal Gmail account. The account must have 'Send as' permissions confirmed for the sending domain.
  • Gmail templates: Three template variants are required (low, medium, high severity). Templates must be approved by the support manager before build. Store template HTML in the platform config store, not in the workflow node itself, so they can be updated without a rebuild.
  • Overdue escalation timing: Deadline values must be stored in the config table keyed by severity (e.g. high: 1 hour, medium: 4 hours, low: 24 hours). The time-based check should use the platform's built-in scheduler or a polling loop against zendesk.ticket.updated_at and zendesk.ticket.status. Confirm deadline values with Priya Sandhu before build.
  • High-severity manager alert: The manager Slack message must include complaint summary (first 200 characters of ticket description), customer tier, prior complaint count, and a direct Zendesk ticket URL. Include a prompt string: 'Please review within 1 hour.'
  • Escalation rate limit: Zendesk REST API rate limit is 700 requests per minute on Suite Team. At 90 complaints per month this is well within limits. Gmail API send limit is 2,000 messages per day for Workspace accounts. Both are safe at current volume.
  • Confirm before build: Routing matrix agreed with support manager, Slack bot installed and channel IDs confirmed, Gmail OAuth2 credentials issued, overdue deadline values signed off, and all three acknowledgement email templates approved.
Developer Handover PackPage 2 of 4
FS-DOC-04Technical

03End-to-end data flow

Full trigger-to-output data flow: Complaint Escalation Workflow
// ── TRIGGER ──────────────────────────────────────────────────────────────
// Event: Customer submits complaint via email or web form
// Source: Gmail inbound parse webhook OR web form POST to Zendesk API

inbound.from_email    : string   // customer email address
inbound.from_name     : string   // customer display name
inbound.subject       : string   // email subject or form title
inbound.body_text     : string   // plain-text complaint content
inbound.body_html     : string   // HTML version (if email)
inbound.attachments[] : file[]   // any attachments
inbound.received_at   : ISO8601  // UTC timestamp of receipt

// ── STEP 1: ZENDESK TICKET CREATION ─────────────────────────────────────
// Action: Automation platform calls Zendesk Tickets API (POST /api/v2/tickets)

zendesk.ticket.id            <- generated by Zendesk
zendesk.ticket.subject       <- inbound.subject
zendesk.ticket.description   <- inbound.body_text
zendesk.ticket.requester_email <- inbound.from_email
zendesk.ticket.requester_name  <- inbound.from_name
zendesk.ticket.status        <- 'new'
zendesk.ticket.priority      <- 'normal'  // default, overridden by agent
zendesk.ticket.created_at    <- inbound.received_at
zendesk.ticket.tags[]        <- ['auto_intake']

// ── HANDOFF: Zendesk ticket.created event fires ───────────────────────
// Webhook payload delivered to automation platform inbound endpoint
// Complaint Triage Agent begins

// ── STEP 2: HUBSPOT CONTACT LOOKUP ───────────────────────────────────
// Action: GET /crm/v3/objects/contacts/search
// Filter: email eq zendesk.ticket.requester_email

hubspot.contact.id                  <- API response or null
hubspot.contact.lifecycle_stage     <- API response or 'unknown'
hubspot.contact.num_contacted_notes <- API response or 0
hubspot.contact.recent_deal_amount  <- API response or 0

// Derive customer tier from recent_deal_amount
triage.customer_tier <- IF recent_deal_amount >= 1000 THEN 'vip'
                        ELSE IF recent_deal_amount >= 200 THEN 'valued'
                        ELSE 'standard'

// Fallback: if no HubSpot contact found
triage.hubspot_matched   <- false
triage.customer_tier     <- 'standard'
triage.prior_complaints  <- 0

// ── STEP 3: SEVERITY SCORING ─────────────────────────────────────────
// Action: Sentiment analysis on zendesk.ticket.description
// Keyword matching against configurable ruleset in config store

triage.sentiment_label   <- enum['negative','highly_negative','neutral']
triage.keyword_flags[]   <- matched severity keywords

// Scoring logic
triage.severity_score <- IF sentiment == 'highly_negative'
                             AND prior_complaints >= 2 THEN 'high'
                         ELSE IF sentiment == 'highly_negative' THEN 'medium'
                         ELSE 'low'

// Override: vip customer_tier always promotes severity one level up
triage.severity_score <- IF customer_tier == 'vip'
                             AND severity == 'low' THEN 'medium'
                         ELSE severity_score

triage.complaint_category <- enum['billing','product','delivery','conduct','other']
// Category assigned by keyword classifier in config ruleset

// ── STEP 4: WRITE TRIAGE OUTPUT TO ZENDESK ───────────────────────────
// Action: PUT /api/v2/tickets/{id}
// Update custom fields on Zendesk ticket

zendesk.ticket.custom_fields['severity_score']     <- triage.severity_score
zendesk.ticket.custom_fields['complaint_category'] <- triage.complaint_category
zendesk.ticket.custom_fields['customer_tier']      <- triage.customer_tier
zendesk.ticket.custom_fields['prior_complaints']   <- triage.prior_complaints
zendesk.ticket.custom_fields['hubspot_contact_id'] <- hubspot.contact.id
zendesk.ticket.custom_fields['triage_timestamp']   <- ISO8601 now()

triage.triage_timestamp <- ISO8601 now()

// ── HANDOFF: Triage Agent complete ───────────────────────────────────
// Escalation and Routing Agent begins

// ── STEP 5: TICKET ROUTING AND ASSIGNMENT ────────────────────────────
// Action: Lookup routing.matrix[complaint_category][severity_score]
// Returns zendesk.group_id and zendesk.assignee_id from config store

zendesk.ticket.group_id    <- routing.matrix lookup result
zendesk.ticket.assignee_id <- routing.matrix lookup result
zendesk.ticket.priority    <- IF severity == 'high' THEN 'high'
                              ELSE IF severity == 'medium' THEN 'normal'
                              ELSE 'low'
zendesk.ticket.status      <- 'open'

// Action: PUT /api/v2/tickets/{id} to apply assignment

// ── STEP 6: SLACK AGENT NOTIFICATION ─────────────────────────────────
// Action: POST to Slack Web API chat.postMessage

slack.agent_dm.channel  <- resolved from routing.matrix assignee Slack user ID
slack.agent_dm.text     <- 'New {severity} complaint assigned: {ticket_url}'
slack.agent_dm.blocks[] <- [{type:'section', text: complaint_summary_200_chars},
                             {type:'section', text: 'Category: {category}'},
                             {type:'section', text: 'Customer tier: {tier}'},
                             {type:'actions',  elements:[{ticket_url_button}]}]

// ── STEP 7: SLACK MANAGER ALERT (high-severity only) ─────────────────
// Condition: triage.severity_score == 'high'

slack.manager_alert.channel  <- config.slack.manager_channel_id
                             // e.g. '#support-escalations' channel ID
slack.manager_alert.text     <- 'HIGH severity complaint requires review'
slack.manager_alert.blocks[] <- [{complaint_summary_200_chars},
                                  {customer_tier}, {prior_complaints},
                                  {ticket_url_button},
                                  {text:'Please review within 1 hour.'}]

// ── STEP 8: GMAIL ACKNOWLEDGEMENT EMAIL ──────────────────────────────
// Action: Gmail API POST /gmail/v1/users/me/messages/send

gmail.message.to          <- zendesk.ticket.requester_email
gmail.message.from        <- config.gmail.send_as_address
                         // e.g. 'support@[YourCompany.com]'
gmail.message.subject     <- template[severity_score].subject
gmail.message.body_html   <- template[severity_score].body_html
                         // Variables injected: {requester_name},
                         //   {ticket_id}, {assignee_name}, {eta_string}
gmail.message.headers['X-ZD-Ticket-ID'] <- zendesk.ticket.id

// ── STEP 9: OVERDUE ESCALATION TIMER ─────────────────────────────────
// Action: Schedule time-based check from triage.triage_timestamp
// Deadline sourced from config: high=1hr, medium=4hr, low=24hr

timer.deadline <- triage.triage_timestamp + config.deadline[severity_score]

// At timer.deadline: poll Zendesk GET /api/v2/tickets/{id}
// Check: zendesk.ticket.status NOT IN ['solved','pending','closed']

// If overdue:
zendesk.ticket.priority          <- 'urgent'  // PATCH /api/v2/tickets/{id}
zendesk.ticket.tags[]            <- append 'overdue_escalation'

slack.overdue_alert.channel      <- config.slack.manager_channel_id
slack.overdue_alert.text         <- 'OVERDUE: Ticket #{ticket_id} past deadline'
slack.overdue_alert.blocks[]     <- [{ticket_id}, {time_overdue},
                                      {current_assignee}, {ticket_url_button}]

// ── END OF AUTOMATED FLOW ────────────────────────────────────────────
// Remaining human step: Support agent resolves complaint and closes ticket
// Step 10: Agent updates zendesk.ticket.status <- 'solved'
//          Optional: Notion resolution log entry written by agent manually
Developer Handover PackPage 3 of 4
FS-DOC-04Technical

04Recommended build stack

Orchestration layer
A workflow automation tool (platform to be confirmed at project kickoff). Recommended pattern: one workflow per agent, meaning one workflow for the Complaint Triage Agent and a separate workflow for the Escalation and Routing Agent. A shared credential store is used across both workflows to hold all API keys, OAuth tokens, Slack channel IDs, routing matrix, and template variants. This prevents credential duplication and allows config changes without editing individual workflow nodes.
Webhook configuration
Zendesk webhook target: registered on the Zendesk admin subdomain under Settings, Webhooks. Target URL is the automation platform's inbound trigger endpoint for the Triage Agent workflow. Payload format: JSON, signed with the Zendesk webhook signing secret (HMAC-SHA256). The platform must validate the X-Zendesk-Webhook-Signature header on every inbound request. A second webhook trigger (or internal chained step) fires the Escalation and Routing Agent once triage fields are confirmed written. Gmail inbound parse (if required for web form submissions via email): configured via the platform's email parse trigger, forwarding to the Zendesk ticket creation step.
Templating approach
All customer-facing email copy and Slack message block structures are stored as parameterised templates in the shared config store, not inside workflow nodes. Template variables use double-brace syntax: {{requester_name}}, {{ticket_id}}, {{assignee_name}}, {{eta_string}}, {{severity_label}}. Three Gmail template variants (low, medium, high) must be approved and stored before build. Slack block kit JSON for agent and manager notifications is also stored in config, allowing content updates without a workflow rebuild.
Error logging
All agent errors (API call failures, HubSpot lookup timeouts, Slack delivery failures, Gmail send errors) are caught at each step and written to a dedicated error log table in Supabase. Table schema: error_id (uuid), workflow_name (string), step_name (string), zendesk_ticket_id (string), error_code (string), error_message (text), payload_snapshot (jsonb), occurred_at (timestamptz). A Slack alert fires to the FullSpec monitoring channel (#fs-error-alerts) whenever a new error row is inserted. The support team is not alerted for individual errors unless a ticket has stalled for more than 10 minutes without triage output.
Testing approach
All agent builds are tested in sandbox-first order: Zendesk sandbox environment (enabled under Admin, Account, Sandbox) is used for all ticket creation, field write, and assignment tests. HubSpot sandbox account is used for all contact lookup tests. Slack test workspace (or a #fs-test-channel in the live workspace) receives all notification outputs during QA. Gmail test send uses a dedicated internal address before switching to the live support@ address. No live customer data is used during the build phase. Real past complaint tickets (anonymised) are used only in the calibration phase after sandbox sign-off.
Estimated total build time
Complaint Triage Agent: 10 hours. Escalation and Routing Agent: 14 hours. End-to-end integration testing, sandbox calibration, and routing matrix configuration: 4 hours. Total: 28 hours. This matches the confirmed build effort in the project template. Delivery is scoped across four weeks per the delivery plan, with agent builds in weeks 2 and 3 and testing and calibration in week 4.
Before any build node is wired: confirm Zendesk tier (Suite Team or above), confirm HubSpot API key with contacts.read and crm.objects.deals.read scopes, confirm Gmail OAuth2 credentials for the support@ send address, confirm Slack bot OAuth installation with chat:write and users:read scopes, confirm all Zendesk custom field IDs are created and recorded in the shared credential store, confirm routing matrix and severity thresholds are signed off by Priya Sandhu (Support Manager), and confirm overdue deadline values per severity tier. Build should not begin on either agent until all of these are resolved. Contact support@gofullspec.com if any access item is blocked.
Developer Handover PackPage 4 of 4

More documents for this process

Every document generated for Complaint Escalation Workflow.

Launch Plan
Operations · Owner
View
ROI and Business Case
Finance · Owner
View
Process Runbook / SOP
Operations · Owner
View
Integration and API Spec
Technical · Developer
View
Test and QA Plan
Quality · Developer
View