diff --git a/fern/assistants/examples/support-escalation.mdx b/fern/assistants/examples/support-escalation.mdx new file mode 100644 index 00000000..0e25524c --- /dev/null +++ b/fern/assistants/examples/support-escalation.mdx @@ -0,0 +1,987 @@ +--- +title: Customer support escalation system +subtitle: Build intelligent support routing using assistants that escalate calls based on customer tier, issue complexity, and agent expertise. +slug: assistants/examples/support-escalation +description: Build a voice AI customer support system with dynamic escalation that routes calls based on customer data, issue type, and real-time agent availability using transfer tools and webhooks. +--- + +## Overview + +Build an intelligent customer support escalation system that determines transfer destinations dynamically using customer tier analysis, issue complexity assessment, and real-time agent availability. This approach uses transfer tools with empty destinations and webhook servers for maximum escalation flexibility. + +**Agent Capabilities:** +* Customer tier-based prioritization and routing +* Issue complexity analysis for specialist routing +* Real-time agent availability and expertise matching +* Intelligent escalation with context preservation + +**What You'll Build:** +* Transfer tool with dynamic escalation logic +* Assistant with intelligent support conversation flow +* Webhook server for escalation destination logic +* CRM integration for customer tier-based routing + +## Prerequisites + +* A [Vapi account](https://dashboard.vapi.ai/) +* Node.js or Python server environment +* (Optional) CRM or customer database for tier lookup + +## Scenario + +We will build a customer support escalation system for TechCorp that intelligently routes support calls based on customer tier, issue complexity, and agent expertise in real-time. + +--- + +## 1. Create a Dynamic Escalation Tool + + + + + + In your Vapi dashboard, click **Tools** in the left sidebar. + + + - Click **Create Tool** + - Select **Transfer Call** as the tool type + - Set tool name: `Smart Support Escalation` + - **Important**: Leave the destinations array empty - this creates a dynamic transfer tool + - Set function name: `escalateToSupport` + - Add description: `Escalate calls to appropriate support specialists based on customer tier and issue complexity` + + + Add these parameters to help the assistant provide context: + - `issue_category` (string): Category of customer issue (technical, billing, account, product) + - `complexity_level` (string): Issue complexity (basic, intermediate, advanced, critical) + - `customer_context` (string): Relevant customer information for routing + - `escalation_reason` (string): Why this needs escalation vs self-service + + + + + ```typescript + import { VapiClient } from "@vapi-ai/server-sdk"; + + const vapi = new VapiClient({ token: process.env.VAPI_API_KEY }); + + async function createSupportEscalationTool() { + try { + const tool = await vapi.tools.create({ + type: "transferCall", + // Empty destinations array makes this a dynamic transfer tool + destinations: [], + function: { + name: "escalateToSupport", + description: "Escalate calls to appropriate support specialists based on customer tier and issue complexity", + parameters: { + type: "object", + properties: { + issue_category: { + type: "string", + description: "Category of customer issue", + enum: ["technical", "billing", "account", "product"] + }, + complexity_level: { + type: "string", + description: "Issue complexity level", + enum: ["basic", "intermediate", "advanced", "critical"] + }, + customer_context: { + type: "string", + description: "Relevant customer information for routing" + }, + escalation_reason: { + type: "string", + description: "Why this needs escalation vs self-service" + } + }, + required: ["issue_category", "complexity_level"] + } + } + }); + + console.log(`Support escalation tool created: ${tool.id}`); + return tool; + } catch (error) { + console.error('Error creating support escalation tool:', error); + throw error; + } + } + + // Create the support escalation tool + const escalationTool = await createSupportEscalationTool(); + ``` + + + ```python + import os + import requests + + def create_support_escalation_tool(): + """Create a dynamic support escalation tool with empty destinations""" + url = "https://api.vapi.ai/tool" + headers = { + "Authorization": f"Bearer {os.getenv('VAPI_API_KEY')}", + "Content-Type": "application/json" + } + + data = { + "type": "transferCall", + # Empty destinations array makes this a dynamic transfer tool + "destinations": [], + "function": { + "name": "escalateToSupport", + "description": "Escalate calls to appropriate support specialists based on customer tier and issue complexity", + "parameters": { + "type": "object", + "properties": { + "issue_category": { + "type": "string", + "description": "Category of customer issue", + "enum": ["technical", "billing", "account", "product"] + }, + "complexity_level": { + "type": "string", + "description": "Issue complexity level", + "enum": ["basic", "intermediate", "advanced", "critical"] + }, + "customer_context": { + "type": "string", + "description": "Relevant customer information for routing" + }, + "escalation_reason": { + "type": "string", + "description": "Why this needs escalation vs self-service" + } + }, + "required": ["issue_category", "complexity_level"] + } + } + } + + try: + response = requests.post(url, headers=headers, json=data) + response.raise_for_status() + tool = response.json() + print(f"Support escalation tool created: {tool['id']}") + return tool + except requests.exceptions.RequestException as error: + print(f"Error creating support escalation tool: {error}") + raise + + # Create the support escalation tool + escalation_tool = create_support_escalation_tool() + ``` + + + ```bash + curl -X POST https://api.vapi.ai/tool \ + -H "Authorization: Bearer $VAPI_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "transferCall", + "destinations": [], + "function": { + "name": "escalateToSupport", + "description": "Escalate calls to appropriate support specialists based on customer tier and issue complexity", + "parameters": { + "type": "object", + "properties": { + "issue_category": { + "type": "string", + "description": "Category of customer issue", + "enum": ["technical", "billing", "account", "product"] + }, + "complexity_level": { + "type": "string", + "description": "Issue complexity level", + "enum": ["basic", "intermediate", "advanced", "critical"] + }, + "customer_context": { + "type": "string", + "description": "Relevant customer information for routing" + }, + "escalation_reason": { + "type": "string", + "description": "Why this needs escalation vs self-service" + } + }, + "required": ["issue_category", "complexity_level"] + } + } + }' + ``` + + + +--- + +## 2. Create an Assistant with Smart Escalation + + + + + + - Navigate to **Assistants** in your dashboard + - Click **Create Assistant** + - Name: `TechCorp Support Assistant` + - Add your dynamic escalation tool to the assistant's tools + + + ```txt title="System Prompt" maxLines=15 + You are TechCorp's intelligent customer support assistant. Your job is to: + + 1. Help customers resolve issues when possible + 2. Assess issue complexity and customer needs + 3. Escalate to human specialists when appropriate using the escalateToSupport function + + Try to resolve simple issues first. For complex issues or when customers request human help, escalate intelligently based on: + - Issue category (technical, billing, account, product) + - Complexity level (basic, intermediate, advanced, critical) + - Customer context and history + + Always be professional and efficient in your support. + ``` + + + In assistant settings, enable the **transfer-destination-request** server event. This sends webhooks to your server when escalations are triggered. + + + Configure your server URL to handle escalation requests (e.g., `https://your-app.com/webhook/escalation`) + + + + + ```typescript + import { VapiClient } from "@vapi-ai/server-sdk"; + + const vapi = new VapiClient({ token: process.env.VAPI_API_KEY }); + + async function createSupportAssistant(escalationToolId: string) { + try { + const assistant = await vapi.assistants.create({ + name: "TechCorp Support Assistant", + firstMessage: "Hello! I'm here to help with your TechCorp support needs. I can assist with account questions, technical issues, billing inquiries, and more. What can I help you with today?", + model: { + provider: "openai", + model: "gpt-4o", + messages: [ + { + role: "system", + content: `You are TechCorp's intelligent customer support assistant. Your job is to: + +1. Help customers resolve issues when possible +2. Assess issue complexity and customer needs +3. Escalate to human specialists when appropriate using the escalateToSupport function + +Try to resolve simple issues first. For complex issues or when customers request human help, escalate intelligently based on: +- Issue category (technical, billing, account, product) +- Complexity level (basic, intermediate, advanced, critical) +- Customer context and history + +Always be professional and efficient in your support.` + } + ], + toolIds: [escalationToolId] + }, + voice: { + provider: "11labs", + voiceId: "burt" + }, + serverUrl: "https://your-app.com/webhook/escalation", + serverUrlSecret: process.env.WEBHOOK_SECRET + }); + + console.log(`Support assistant created: ${assistant.id}`); + return assistant; + } catch (error) { + console.error('Error creating support assistant:', error); + throw error; + } + } + + // Create assistant with escalation capabilities + const supportAssistant = await createSupportAssistant("YOUR_ESCALATION_TOOL_ID"); + ``` + + + ```python + import os + import requests + + def create_support_assistant(escalation_tool_id): + """Create assistant with dynamic escalation capabilities""" + url = "https://api.vapi.ai/assistant" + headers = { + "Authorization": f"Bearer {os.getenv('VAPI_API_KEY')}", + "Content-Type": "application/json" + } + + data = { + "name": "TechCorp Support Assistant", + "firstMessage": "Hello! I'\''m here to help with your TechCorp support needs. I can assist with account questions, technical issues, billing inquiries, and more. What can I help you with today?", + "model": { + "provider": "openai", + "model": "gpt-4o", + "messages": [ + { + "role": "system", + "content": """You are TechCorp's intelligent customer support assistant. Your job is to: + +1. Help customers resolve issues when possible +2. Assess issue complexity and customer needs +3. Escalate to human specialists when appropriate using the escalateToSupport function + +Try to resolve simple issues first. For complex issues or when customers request human help, escalate intelligently based on: +- Issue category (technical, billing, account, product) +- Complexity level (basic, intermediate, advanced, critical) +- Customer context and history + +Always be professional and efficient in your support.""" + } + ], + "toolIds": [escalation_tool_id] + }, + "voice": { + "provider": "11labs", + "voiceId": "burt" + }, + "serverUrl": "https://your-app.com/webhook/escalation", + "serverUrlSecret": os.getenv("WEBHOOK_SECRET") + } + + try: + response = requests.post(url, headers=headers, json=data) + response.raise_for_status() + assistant = response.json() + print(f"Support assistant created: {assistant['id']}") + return assistant + except requests.exceptions.RequestException as error: + print(f"Error creating support assistant: {error}") + raise + + # Create assistant with escalation capabilities + support_assistant = create_support_assistant("YOUR_ESCALATION_TOOL_ID") + ``` + + + ```bash + curl -X POST https://api.vapi.ai/assistant \ + -H "Authorization: Bearer $VAPI_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "TechCorp Support Assistant", + "firstMessage": "Hello! I'\''m here to help with your TechCorp support needs. I can assist with account questions, technical issues, billing inquiries, and more. What can I help you with today?", + "model": { + "provider": "openai", + "model": "gpt-4o", + "messages": [ + { + "role": "system", + "content": "You are TechCorp'\''s intelligent customer support assistant. Your job is to:\n\n1. Help customers resolve issues when possible\n2. Assess issue complexity and customer needs\n3. Escalate to human specialists when appropriate using the escalateToSupport function\n\nTry to resolve simple issues first. For complex issues or when customers request human help, escalate intelligently based on:\n- Issue category (technical, billing, account, product)\n- Complexity level (basic, intermediate, advanced, critical)\n- Customer context and history\n\nAlways be professional and efficient in your support." + } + ], + "toolIds": ["YOUR_ESCALATION_TOOL_ID"] + }, + "voice": { + "provider": "11labs", + "voiceId": "burt" + }, + "serverUrl": "https://your-app.com/webhook/escalation", + "serverUrlSecret": "your-webhook-secret" + }' + ``` + + + +--- + +## 3. Build Escalation Logic Server + + + + ```typescript + import express from 'express'; + import crypto from 'crypto'; + + const app = express(); + app.use(express.json()); + + // Webhook secret verification + function verifyWebhookSignature(payload: string, signature: string) { + const expectedSignature = crypto + .createHmac('sha256', process.env.WEBHOOK_SECRET!) + .update(payload) + .digest('hex'); + return crypto.timingSafeEqual( + Buffer.from(signature), + Buffer.from(expectedSignature) + ); + } + + // Support escalation logic + function determineSupportDestination(request: any) { + const { functionCall, call, customer } = request; + const { issue_category, complexity_level, customer_context, escalation_reason } = functionCall.parameters; + + // Simulate customer tier lookup + const customerData = lookupCustomerTier(customer.number); + + // Enterprise customer escalation + if (customerData?.tier === 'enterprise' || complexity_level === 'critical') { + return { + type: "number", + number: "+1-555-ENTERPRISE-SUPPORT", + message: "Connecting you to our enterprise support specialist.", + transferPlan: { + mode: "warm-transfer-say-summary", + summaryPlan: { + enabled: true, + messages: [ + { + role: "system", + content: "Provide a summary for the enterprise support specialist." + }, + { + role: "user", + content: `Enterprise customer with ${issue_category} issue. Complexity: ${complexity_level}. Reason: ${escalation_reason}. Context: ${customer_context}` + } + ] + } + } + }; + } + + // Advanced technical issues + if (issue_category === 'technical' && (complexity_level === 'advanced' || complexity_level === 'intermediate')) { + return { + type: "number", + number: "+1-555-TECH-SPECIALISTS", + message: "Transferring you to our technical support specialists.", + transferPlan: { + mode: "warm-transfer-say-message", + message: `Technical ${complexity_level} issue. Customer context: ${customer_context}. Escalation reason: ${escalation_reason}` + } + }; + } + + // Billing and account specialists + if (issue_category === 'billing' || issue_category === 'account') { + return { + type: "number", + number: "+1-555-BILLING-TEAM", + message: "Connecting you with our billing and account specialists.", + transferPlan: { + mode: "warm-transfer-say-message", + message: `${issue_category} issue, complexity ${complexity_level}. Context: ${customer_context}` + } + }; + } + + // Product and feature questions + if (issue_category === 'product') { + return { + type: "number", + number: "+1-555-PRODUCT-SUPPORT", + message: "Transferring you to our product specialists.", + transferPlan: { + mode: "warm-transfer-say-message", + message: `Product ${complexity_level} inquiry. Context: ${customer_context}` + } + }; + } + + // Default to general support + return { + type: "number", + number: "+1-555-GENERAL-SUPPORT", + message: "Connecting you with our support team.", + transferPlan: { + mode: "warm-transfer-say-message", + message: `General ${issue_category} support needed. Level: ${complexity_level}` + } + }; + } + + // Simulate customer tier lookup + function lookupCustomerTier(phoneNumber: string) { + // In production, integrate with your actual CRM + const mockCustomerData = { + "+1234567890": { tier: "enterprise", account: "TechCorp Enterprise" }, + "+0987654321": { tier: "standard", account: "Basic Plan" }, + "+1111111111": { tier: "premium", account: "Premium Support" } + }; + return mockCustomerData[phoneNumber]; + } + + // Support escalation webhook + app.post('/webhook/escalation', (req, res) => { + try { + const signature = req.headers['x-vapi-signature'] as string; + const payload = JSON.stringify(req.body); + + // Verify webhook signature + if (!verifyWebhookSignature(payload, signature)) { + return res.status(401).json({ error: 'Invalid signature' }); + } + + const request = req.body; + + // Only handle transfer destination requests + if (request.type !== 'transfer-destination-request') { + return res.status(200).json({ received: true }); + } + + // Determine destination based on escalation context + const destination = determineSupportDestination(request); + + res.json({ destination }); + } catch (error) { + console.error('Escalation webhook error:', error); + res.status(500).json({ + error: 'Unable to determine escalation destination. Please try again.' + }); + } + }); + + app.listen(3000, () => { + console.log('Support escalation server running on port 3000'); + }); + ``` + + + ```python + import os + import hmac + import hashlib + from fastapi import FastAPI, HTTPException, Request + from pydantic import BaseModel + from typing import Optional, Dict, Any + + app = FastAPI() + + def verify_webhook_signature(payload: bytes, signature: str) -> bool: + """Verify webhook signature""" + webhook_secret = os.getenv('WEBHOOK_SECRET', '').encode() + expected_signature = hmac.new( + webhook_secret, + payload, + hashlib.sha256 + ).hexdigest() + return hmac.compare_digest(signature, expected_signature) + + def lookup_customer_tier(phone_number: str) -> Optional[Dict[str, Any]]: + """Simulate customer tier lookup""" + mock_customer_data = { + "+1234567890": {"tier": "enterprise", "account": "TechCorp Enterprise"}, + "+0987654321": {"tier": "standard", "account": "Basic Plan"}, + "+1111111111": {"tier": "premium", "account": "Premium Support"} + } + return mock_customer_data.get(phone_number) + + def determine_support_destination(request_data: Dict[str, Any]) -> Dict[str, Any]: + """Determine support escalation destination based on request context""" + function_call = request_data.get('functionCall', {}) + parameters = function_call.get('parameters', {}) + customer = request_data.get('customer', {}) + + issue_category = parameters.get('issue_category', 'general') + complexity_level = parameters.get('complexity_level', 'basic') + customer_context = parameters.get('customer_context', '') + escalation_reason = parameters.get('escalation_reason', '') + + # Simulate customer tier lookup + customer_data = lookup_customer_tier(customer.get('number', '')) + + # Enterprise customer escalation + if (customer_data and customer_data.get('tier') == 'enterprise') or complexity_level == 'critical': + return { + "type": "number", + "number": "+1-555-ENTERPRISE-SUPPORT", + "message": "Connecting you to our enterprise support specialist.", + "transferPlan": { + "mode": "warm-transfer-say-summary", + "summaryPlan": { + "enabled": True, + "messages": [ + { + "role": "system", + "content": "Provide a summary for the enterprise support specialist." + }, + { + "role": "user", + "content": f"Enterprise customer with {issue_category} issue. Complexity: {complexity_level}. Reason: {escalation_reason}. Context: {customer_context}" + } + ] + } + } + } + + # Advanced technical issues + if issue_category == 'technical' and complexity_level in ['advanced', 'intermediate']: + return { + "type": "number", + "number": "+1-555-TECH-SPECIALISTS", + "message": "Transferring you to our technical support specialists.", + "transferPlan": { + "mode": "warm-transfer-say-message", + "message": f"Technical {complexity_level} issue. Customer context: {customer_context}. Escalation reason: {escalation_reason}" + } + } + + # Billing and account specialists + if issue_category in ['billing', 'account']: + return { + "type": "number", + "number": "+1-555-BILLING-TEAM", + "message": "Connecting you with our billing and account specialists.", + "transferPlan": { + "mode": "warm-transfer-say-message", + "message": f"{issue_category} issue, complexity {complexity_level}. Context: {customer_context}" + } + } + + # Product and feature questions + if issue_category == 'product': + return { + "type": "number", + "number": "+1-555-PRODUCT-SUPPORT", + "message": "Transferring you to our product specialists.", + "transferPlan": { + "mode": "warm-transfer-say-message", + "message": f"Product {complexity_level} inquiry. Context: {customer_context}" + } + } + + # Default to general support + return { + "type": "number", + "number": "+1-555-GENERAL-SUPPORT", + "message": "Connecting you with our support team.", + "transferPlan": { + "mode": "warm-transfer-say-message", + "message": f"General {issue_category} support needed. Level: {complexity_level}" + } + } + + @app.post("/webhook/escalation") + async def handle_escalation_webhook(request: Request): + try: + # Get raw body for signature verification + body = await request.body() + signature = request.headers.get('x-vapi-signature', '') + + # Verify webhook signature + if not verify_webhook_signature(body, signature): + raise HTTPException(status_code=401, detail="Invalid signature") + + # Parse request body + request_data = await request.json() + + # Only handle transfer destination requests + if request_data.get('type') != 'transfer-destination-request': + return {"received": True} + + # Determine destination based on escalation context + destination = determine_support_destination(request_data) + + return {"destination": destination} + + except Exception as error: + print(f"Escalation webhook error: {error}") + raise HTTPException( + status_code=500, + detail="Unable to determine escalation destination. Please try again." + ) + + if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=3000) + ``` + + + +--- + +## 4. Test Your Support Escalation System + + + + + + - Navigate to **Phone Numbers** in your dashboard + - Click **Create Phone Number** + - Assign your support assistant to the number + - Configure any additional settings + + + Call your number and test various scenarios: + - Basic technical questions (should try to resolve first) + - Complex billing issues from enterprise customers + - Advanced technical problems requiring specialists + - Critical issues requiring immediate escalation + + + Check your server logs to see: + - Escalation requests received + - Customer tier classifications + - Destination routing decisions + - Any errors or routing issues + + + + + ```typescript + import { VapiClient } from "@vapi-ai/server-sdk"; + + const vapi = new VapiClient({ token: process.env.VAPI_API_KEY }); + + async function testSupportEscalation(assistantId: string) { + try { + // Test enterprise customer with complex issue + const enterpriseCall = await vapi.calls.create({ + assistantId: assistantId, + customer: { + number: "+1234567890", // Enterprise customer in your lookup + name: "Enterprise Customer - Technical Issue" + } + }); + + console.log(`Enterprise test call created: ${enterpriseCall.id}`); + + // Test standard customer with billing question + const standardCall = await vapi.calls.create({ + assistantId: assistantId, + customer: { + number: "+0987654321", // Standard customer in your lookup + name: "Standard Customer - Billing Question" + } + }); + + console.log(`Standard test call created: ${standardCall.id}`); + + return { enterpriseCall, standardCall }; + } catch (error) { + console.error('Error creating test calls:', error); + throw error; + } + } + + // Test the support escalation system + const testCalls = await testSupportEscalation('YOUR_ASSISTANT_ID'); + ``` + + + ```python + import requests + import os + + def test_support_escalation(assistant_id): + """Test support escalation with different customer scenarios""" + url = "https://api.vapi.ai/call" + headers = { + "Authorization": f"Bearer {os.getenv('VAPI_API_KEY')}", + "Content-Type": "application/json" + } + + test_scenarios = [ + { + "name": "Enterprise Technical Issue", + "customer": { + "number": "+1234567890", # Enterprise customer + "name": "Enterprise Customer - Technical Issue" + } + }, + { + "name": "Standard Billing Question", + "customer": { + "number": "+0987654321", # Standard customer + "name": "Standard Customer - Billing Question" + } + } + ] + + results = [] + for scenario in test_scenarios: + try: + data = { + "assistantId": assistant_id, + **scenario + } + + response = requests.post(url, headers=headers, json=data) + response.raise_for_status() + call = response.json() + + print(f"{scenario['name']} call created: {call['id']}") + results.append(call) + + except requests.exceptions.RequestException as error: + print(f"Error creating {scenario['name']}: {error}") + + return results + + # Test the support escalation system + test_calls = test_support_escalation('YOUR_ASSISTANT_ID') + ``` + + + +## Advanced Integration Examples + +### CRM Integration (Salesforce) + +```typescript +// Example: Salesforce CRM integration for customer tier lookup +async function lookupCustomerInSalesforce(phoneNumber: string) { + const salesforce = new SalesforceAPI({ + clientId: process.env.SALESFORCE_CLIENT_ID, + clientSecret: process.env.SALESFORCE_CLIENT_SECRET, + redirectUri: process.env.SALESFORCE_REDIRECT_URI + }); + + try { + const customer = await salesforce.query(` + SELECT Id, Account.Type, Support_Tier__c, Case_Count__c, Contract_Level__c + FROM Contact + WHERE Phone = '${phoneNumber}' + `); + + return customer.records[0]; + } catch (error) { + console.error('Salesforce lookup failed:', error); + return null; + } +} +``` + +### Issue Complexity Assessment + +```typescript +function assessIssueComplexity(issueDescription: string, customerHistory: any) { + const complexKeywords = ['api', 'integration', 'custom', 'enterprise', 'migration']; + const criticalKeywords = ['down', 'outage', 'critical', 'urgent', 'emergency']; + + const hasComplexKeywords = complexKeywords.some(keyword => + issueDescription.toLowerCase().includes(keyword) + ); + + const hasCriticalKeywords = criticalKeywords.some(keyword => + issueDescription.toLowerCase().includes(keyword) + ); + + if (hasCriticalKeywords || customerHistory.previousEscalations > 2) { + return 'critical'; + } + + if (hasComplexKeywords || customerHistory.tier === 'enterprise') { + return 'advanced'; + } + + return 'basic'; +} +``` + +### Agent Availability Checking + +```typescript +function getAvailableSpecialist(category: string, complexity: string) { + const specialists = getSpecialistsByCategory(category); + const qualifiedAgents = specialists.filter(agent => + agent.complexityLevel >= complexity && agent.isAvailable + ); + + if (qualifiedAgents.length === 0) { + return { + type: "number", + number: "+1-555-QUEUE-CALLBACK", + message: "All specialists are busy. You'll be added to our priority queue.", + transferPlan: { + mode: "warm-transfer-say-message", + message: `${category} ${complexity} issue - customer needs callback when specialist available` + } + }; + } + + // Return least busy qualified agent + const bestAgent = qualifiedAgents.sort( + (a, b) => a.activeCallCount - b.activeCallCount + )[0]; + + return { + type: "number", + number: bestAgent.phoneNumber, + message: `Connecting you to ${bestAgent.name}, our ${category} specialist.`, + transferPlan: { + mode: "warm-transfer-say-summary", + summaryPlan: { + enabled: true, + messages: [ + { + role: "system", + content: `Provide a summary for ${bestAgent.name}` + } + ] + } + } + }; +} +``` + +## Error Handling Best Practices + +### Comprehensive Error Handling + +```typescript +function handleEscalationError(error: any, context: any) { + console.error('Support escalation error:', error); + + // Log escalation details for debugging + console.error('Escalation context:', { + phoneNumber: context.customer?.number, + issueCategory: context.functionCall?.parameters?.issue_category, + complexityLevel: context.functionCall?.parameters?.complexity_level, + timestamp: new Date().toISOString() + }); + + // Return fallback destination + return { + type: "number", + number: process.env.FALLBACK_SUPPORT_NUMBER, + message: "I'll connect you with our general support team who can help you.", + transferPlan: { + mode: "warm-transfer-say-message", + message: "Escalation routing error - connecting to general support team" + } + }; +} +``` + +### Queue Management + +```typescript +async function getEscalationWithQueueManagement(context: any) { + try { + const queueStatus = await checkSupportQueueStatus(); + const destination = await determineEscalationDestination(context); + + // Add queue time estimate if available + if (queueStatus.estimatedWaitTime > 5) { + destination.message += ` Current wait time is approximately ${queueStatus.estimatedWaitTime} minutes.`; + } + + return destination; + } catch (error) { + return handleEscalationError(error, context); + } +} +``` + +## Next Steps + +You've built a sophisticated customer support escalation system using assistants! Consider these enhancements: + +* **[Property management call routing](/workflows/examples/property-management)** - Explore the visual workflow approach +* **[Call Analysis](/assistants/call-analysis)** - Analyze escalation patterns and optimize routing +* **[Custom Tools](/tools/custom-tools)** - Build additional tools for advanced support logic +* **[Webhooks](/server-url)** - Learn more about webhook security and advanced event handling diff --git a/fern/calls/call-dynamic-transfers.mdx b/fern/calls/call-dynamic-transfers.mdx index 71c79e1f..3ca22036 100644 --- a/fern/calls/call-dynamic-transfers.mdx +++ b/fern/calls/call-dynamic-transfers.mdx @@ -1,115 +1,87 @@ --- -title: Dynamic Call Transfers +title: Dynamic call transfers +subtitle: Route calls to different destinations based on real-time conversation context and external data. slug: calls/call-dynamic-transfers +description: Learn how Vapi's dynamic call transfers work and explore implementation patterns for intelligent call routing. --- -## Before you start +## Overview -Prerequisites: -- Access to a server or cloud function that can receive requests from Vapi +Dynamic call transfers enable intelligent routing by determining transfer destinations in real-time based on conversation context, customer data, or external system information. Unlike static transfers with predefined destinations, dynamic transfers make routing decisions on-the-fly during the call. -## Overview +**Key capabilities:** +* Real-time destination selection based on conversation analysis +* Integration with CRM systems, databases, and external APIs +* Conditional routing logic for departments, specialists, or geographic regions +* Context-aware transfers with conversation summaries +* Fallback handling for unavailable destinations -Dynamic call transfers let your assistant transfer calls to different destinations (phone numbers, SIP, or other assistants) based on real-time context. This guide shows you how to set up a custom transfer tool, connect it to your assistant, handle transfer requests with your server, and respond with the right destination or error. +## How It Works - - - Create a transfer tool with an empty `destinations` array. This acts as a placeholder so you can define destinations dynamically at runtime. - - ```bash - curl -X POST https://api.vapi.ai/tool \ - -H "Authorization: Bearer insert-private-key-here" \ - -H "Content-Type: application/json" \ - -d '{ - "type": "transferCall", - "destinations": [], - "function": { - "name": "dynamicDestinationTransferCall" - } - }' - ``` - - - - After creating the tool, link it to your assistant. This enables the assistant to trigger the tool during calls. - - - - In your assistant settings, select the `transfer-destination-request` server event. This event sends a webhook to your server whenever a transfer is requested, so you can dynamically determine the destination. - - - - Update your assistant's server URL to point to your server. Your server will receive a webhook with call details whenever a transfer is triggered, and should respond with the appropriate destination or an error. - - - - Use a prompt like this to trigger the transfer tool: - - ``` - [TASK] - trigger the dynamicDestinationTransferCall tool - ``` - - When triggered, the assistant sends a `transfer-destination-request` webhook to your server. The webhook includes call details, transcripts, and messages. - - **Sample request payload:** - ```json - { - "type": "transfer-destination-request", - "artifact": { - "messages": [...], - "transcript": "Hello, how can I help you?", - "messagesOpenAIFormatted": [...] - }, - "assistant": { "id": "assistant123" }, - "phoneNumber": "+14155552671", - "customer": { "id": "customer456" }, - "call": { "id": "call789", "status": "ongoing" } - } - ``` - - - - Your server should respond with either a valid `destination` or an `error`. - - #### Number destination example - ```json - { - "destination": { - "type": "number", - "message": "Connecting you to our support line.", - "number": "+14155552671", - "numberE164CheckEnabled": true, - "callerId": "+14155551234", - "extension": "101" - } - } - ``` +Dynamic transfers operate by leaving the destination unspecified initially, then using webhooks to determine the appropriate destination when needed. + +**Transfer flow:** +1. **Trigger** - Voice agent determines a transfer is needed based on conversation +2. **Webhook** - Vapi sends `transfer-destination-request` to your server with call context +3. **Decision** - Your server analyzes context and external data to determine routing +4. **Response** - Server returns destination details and transfer configuration +5. **Transfer** - Vapi executes the transfer to the determined destination + +**Available context:** Your webhook receives conversation transcript, extracted variables, customer information, function parameters, and call metadata. + +## Implementation Approaches + +**Assistant-based implementation** uses transfer-type tools with conditions interpreted by the assistant through system prompts. The assistant determines when and where to route calls based on clearly defined tool purposes and routing logic in the prompt. Best for quick setup and simpler routing scenarios. + +**Workflow-based implementation** uses conditional logic based on outputs from any workflow node - tools, API requests, conversation variables, or other data sources. Conditions evaluate node outputs to determine routing paths within visual workflows. Best for complex business logic, structured decision trees, and team-friendly configuration. + + + +
+ +
+ **Assistant-based routing** - #### SIP destination example - ```json - { - "destination": { - "type": "sip", - "message": "Connecting your call via SIP.", - "sipUri": "sip:customer-support@domain.com", - "sipHeaders": { - "X-Custom-Header": "value" - } - } - } - ``` + Route customers to appropriate support tiers based on conversation analysis and customer data +
+ +
+ +
+ **Workflow-based routing** - #### Error response example - ```json - { - "error": "Invalid destination specified." - } - ``` - Every response must include either a `destination` or an `error` to indicate the outcome of the transfer request. -
-
- -## Conclusion - -Dynamic call transfers empower your assistant to route calls efficiently based on real-time data. By implementing this flow, you can ensure seamless interactions and provide a better experience for your users. + Direct tenant calls to the right department with automated verification + + + +## Routing Patterns + +### Common Use Cases + +* **Customer support routing** - Route based on issue type, customer tier, agent availability, and interaction history. Enterprise customers and critical issues get priority routing to specialized teams. + +* **Geographic routing** - Direct calls to regional offices based on customer location and business hours. Automatically handle time zone differences and language preferences. + +* **Load balancing** - Distribute calls across available agents to optimize wait times and agent utilization. Route to the least busy qualified agent. + +* **Escalation management** - Implement intelligent escalation based on conversation tone, issue complexity, and customer history. Automatically route urgent issues to senior agents. + +### Transfer Configuration + +1. **Warm transfers** provide context to receiving agents with AI-generated conversation summaries, ensuring smooth handoffs with full context. + +2. **Cold transfers** route calls immediately with predefined context messages, useful for simple departmental routing. + +3. **Conditional transfers** apply different transfer modes based on routing decisions, such as priority handling for enterprise customers. + +4. **Destination types** include phone numbers for human agents, SIP endpoints for VoIP systems, and Vapi assistants for specialized AI agents. + + +**Security considerations:** Always verify webhook signatures to ensure requests come from Vapi. Never log sensitive customer data, implement proper access controls, and follow privacy regulations like GDPR and CCPA when handling customer information in routing decisions. + + +## Related Documentation + +* **[Call Forwarding](/call-forwarding)** - Static transfer options and transfer plans +* **[Webhooks](/server-url)** - Webhook security and event handling patterns +* **[Custom Tools](/tools/custom-tools)** - Build custom tools for advanced routing logic diff --git a/fern/docs.yml b/fern/docs.yml index b900b683..192ada1c 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -208,6 +208,9 @@ navigation: - page: Documentation agent path: assistants/examples/docs-agent.mdx icon: fa-light fa-microphone + - page: Support escalation + path: assistants/examples/support-escalation.mdx + icon: fa-light fa-headset - section: Workflows contents: @@ -232,6 +235,9 @@ navigation: - page: Order management path: workflows/examples/ecommerce-order-management.mdx icon: fa-light fa-shopping-cart + - page: Property management + path: workflows/examples/property-management.mdx + icon: fa-light fa-building - section: Best practices contents: diff --git a/fern/workflows/examples/property-management.mdx b/fern/workflows/examples/property-management.mdx new file mode 100644 index 00000000..970bbb0e --- /dev/null +++ b/fern/workflows/examples/property-management.mdx @@ -0,0 +1,793 @@ +--- +title: Property management call routing +subtitle: Build a call routing workflow for property management that dynamically routes calls based on tenant status, inquiry type, and agent availability. +slug: workflows/examples/property-management +description: Build a voice AI property management system with dynamic call routing that determines destinations based on tenant verification, inquiry type analysis, and real-time agent availability using workflow API requests. +--- + +## Overview + +Build a property management call routing workflow that determines transfer destinations dynamically using tenant verification, inquiry type analysis, and real-time agent availability. This approach uses visual workflow nodes with API Request nodes for maximum routing flexibility. + +**Workflow Capabilities:** +* Tenant status verification and prioritization +* Inquiry type classification for specialist routing +* Real-time agent availability and queue management +* Emergency routing for urgent maintenance issues + +**What You'll Build:** +* Visual workflow with conditional routing logic +* API Request nodes for dynamic destination logic +* Tenant verification with CRM integration +* Emergency escalation with priority queuing + +## Prerequisites + +* A [Vapi account](https://dashboard.vapi.ai/) +* Property management system API or tenant database +* (Optional) Agent availability tracking system + +## Scenario + +We will build a call routing workflow for Riverside Property Management that intelligently routes tenant calls based on their status, inquiry type, and agent availability. + +--- + +## 1. Create a Workflow + + + + + + In your Vapi dashboard, click **Workflows** in the left sidebar. + + + - Click **Create Workflow** + - Name: `Property Management Call Router` + - Select blank template to start with basic call start node + - Click **Create Workflow** + + + Click on the conversation node and configure: + + **First Message**: + ```txt + Hello! You've reached Riverside Property Management. I can help you with maintenance requests, lease questions, rent payments, and general inquiries. How may I assist you today? + ``` + + **System Prompt**: + ```txt + You are a helpful property management assistant. Listen to determine: + - Is this an emergency/urgent maintenance issue? + - What type of inquiry is this (maintenance, lease, rent, general)? + - Do they want to speak to a specific person or department? + + Keep responses under 25 words and be professional. + ``` + + + + + ```typescript + import { VapiClient } from "@vapi-ai/server-sdk"; + + const vapi = new VapiClient({ token: process.env.VAPI_API_KEY }); + + async function createPropertyManagementWorkflow() { + try { + const workflow = await vapi.workflows.create({ + name: "Property Management Call Router", + nodes: [ + { + id: "greeting", + type: "conversation", + firstMessage: "Hello! You've reached Riverside Property Management. I can help you with maintenance requests, lease questions, rent payments, and general inquiries. How may I assist you today?", + systemPrompt: "You are a helpful property management assistant. Listen to determine: Is this an emergency/urgent maintenance issue? What type of inquiry is this (maintenance, lease, rent, general)? Do they want to speak to a specific person or department? Keep responses under 25 words and be professional.", + extractVariables: [ + { + name: "inquiry_type", + type: "string", + description: "Type of inquiry", + enum: ["emergency", "maintenance", "lease", "rent", "general"] + }, + { + name: "caller_phone", + type: "string", + description: "Caller's phone number for tenant lookup" + } + ] + } + ], + edges: [] + }); + + console.log(`Property management workflow created: ${workflow.id}`); + return workflow; + } catch (error) { + console.error('Error creating workflow:', error); + throw error; + } + } + + // Create the workflow + const workflow = await createPropertyManagementWorkflow(); + ``` + + + ```python + import os + import requests + + def create_property_management_workflow(): + """Create property management call routing workflow""" + url = "https://api.vapi.ai/workflow" + headers = { + "Authorization": f"Bearer {os.getenv('VAPI_API_KEY')}", + "Content-Type": "application/json" + } + + data = { + "name": "Property Management Call Router", + "nodes": [ + { + "id": "greeting", + "type": "conversation", + "firstMessage": "Hello! You've reached Riverside Property Management. I can help you with maintenance requests, lease questions, rent payments, and general inquiries. How may I assist you today?", + "systemPrompt": "You are a helpful property management assistant. Listen to determine: Is this an emergency/urgent maintenance issue? What type of inquiry is this (maintenance, lease, rent, general)? Do they want to speak to a specific person or department? Keep responses under 25 words and be professional.", + "extractVariables": [ + { + "name": "inquiry_type", + "type": "string", + "description": "Type of inquiry", + "enum": ["emergency", "maintenance", "lease", "rent", "general"] + }, + { + "name": "caller_phone", + "type": "string", + "description": "Caller's phone number for tenant lookup" + } + ] + } + ], + "edges": [] + } + + try: + response = requests.post(url, headers=headers, json=data) + response.raise_for_status() + workflow = response.json() + print(f"Property management workflow created: {workflow['id']}") + return workflow + except requests.exceptions.RequestException as error: + print(f"Error creating workflow: {error}") + raise + + # Create the workflow + workflow = create_property_management_workflow() + ``` + + + ```bash + curl -X POST https://api.vapi.ai/workflow \ + -H "Authorization: Bearer $VAPI_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Property Management Call Router", + "nodes": [ + { + "id": "greeting", + "type": "conversation", + "firstMessage": "Hello! You'\''ve reached Riverside Property Management. I can help you with maintenance requests, lease questions, rent payments, and general inquiries. How may I assist you today?", + "systemPrompt": "You are a helpful property management assistant. Listen to determine: Is this an emergency/urgent maintenance issue? What type of inquiry is this (maintenance, lease, rent, general)? Do they want to speak to a specific person or department? Keep responses under 25 words and be professional.", + "extractVariables": [ + { + "name": "inquiry_type", + "type": "string", + "description": "Type of inquiry", + "enum": ["emergency", "maintenance", "lease", "rent", "general"] + }, + { + "name": "caller_phone", + "type": "string", + "description": "Caller'\''s phone number for tenant lookup" + } + ] + } + ], + "edges": [] + }' + ``` + + + +--- + +## 2. Add Tenant Verification Node + + + + Add an **API Request** node after the greeting: + + **Node Configuration**: + - Node ID: `tenant_lookup` + - HTTP Method: `POST` + - URL: `https://your-property-system.com/api/tenants/lookup` + - Headers: `Authorization: Bearer YOUR_API_KEY` + - Body: + ```json + { + "phone": "{{caller_phone}}", + "inquiry_type": "{{inquiry_type}}" + } + ``` + + + Map the API response to workflow variables: + - `tenant_status` → Extract from `response.tenant.status` + - `property_address` → Extract from `response.tenant.property` + - `account_standing` → Extract from `response.tenant.account_standing` + - `emergency_contact` → Extract from `response.tenant.emergency_contact` + + + Configure what happens if the API call fails: + - **On Error**: Route to general agent queue + - **Error Message**: "I'll connect you with our general team who can help." + + + +--- + +## 3. Build Emergency Routing Logic + + + + Add a **Conversation** node for emergency handling: + + **Condition**: `inquiry_type == "emergency"` + + **First Message**: + ```txt + I understand this is an emergency. Let me immediately connect you with our emergency maintenance team. Please stay on the line. + ``` + + **System Prompt**: + ```txt + This is an emergency maintenance situation. Confirm the emergency details quickly and route to emergency maintenance immediately. Keep interaction brief. + ``` + + + Add an **API Request** node to get emergency destination: + + **URL**: `https://your-system.com/api/routing/emergency` + **Method**: `POST` + **Body**: + ```json + { + "tenant_id": "{{tenant_id}}", + "property": "{{property_address}}", + "inquiry_type": "emergency", + "priority": "high" + } + ``` + + + Add a **Transfer Call** node: + - **Destination**: Use the phone number from the API response + - **Transfer Plan**: Include emergency context and tenant information + - **Priority**: Set to highest priority for immediate routing + + + +--- + +## 4. Create Inquiry-Based Routing + + + + Add **API Request** node for maintenance team routing: + + **Condition**: `inquiry_type == "maintenance"` + **URL**: `https://your-system.com/api/routing/maintenance` + **Body**: + ```json + { + "tenant_id": "{{tenant_id}}", + "property": "{{property_address}}", + "inquiry_type": "maintenance", + "tenant_status": "{{tenant_status}}" + } + ``` + + Response should include: + - Available maintenance coordinator phone + - Estimated wait time + - Work order creation capability + + + Add **API Request** node for leasing inquiries: + + **Condition**: `inquiry_type == "lease"` + **URL**: `https://your-system.com/api/routing/leasing` + **Body**: + ```json + { + "tenant_id": "{{tenant_id}}", + "property": "{{property_address}}", + "inquiry_type": "lease", + "account_standing": "{{account_standing}}" + } + ``` + + + Add **API Request** node for billing department: + + **Condition**: `inquiry_type == "rent"` + **URL**: `https://your-system.com/api/routing/billing` + **Body**: + ```json + { + "tenant_id": "{{tenant_id}}", + "account_standing": "{{account_standing}}", + "inquiry_type": "rent" + } + ``` + + + +--- + +## 5. Add Agent Availability Logic + + + + Before each transfer, add an **API Request** to check agent availability: + + **URL**: `https://your-system.com/api/agents/availability` + **Method**: `GET` + **Query Parameters**: `department={{department}}&priority={{priority}}` + + Response includes: + - Available agents with phone numbers + - Current queue length + - Estimated wait times + + + Add conditional routing based on availability: + + **If agents available**: Direct transfer to agent + **If queue exists**: Inform caller of wait time and offer callback + **If all busy**: Route to voicemail or priority callback system + + + Add **API Request** node for callback scheduling: + + **URL**: `https://your-system.com/api/callbacks/schedule` + **Body**: + ```json + { + "tenant_id": "{{tenant_id}}", + "phone": "{{caller_phone}}", + "inquiry_type": "{{inquiry_type}}", + "priority": "{{priority}}", + "requested_time": "{{preferred_callback_time}}" + } + ``` + + + +--- + +## 6. Build Transfer Nodes with Context + + + + Use the API response data to populate transfer nodes: + + **Maintenance Transfer**: + - **Destination**: `{{maintenance_agent_phone}}` + - **Message**: "Connecting you to {{agent_name}} from our maintenance team." + - **Transfer Plan**: Include tenant property address and issue details + + **Leasing Transfer**: + - **Destination**: `{{leasing_agent_phone}}` + - **Message**: "Transferring you to our leasing office." + - **Transfer Plan**: Include tenant status and lease information + + **Billing Transfer**: + - **Destination**: `{{billing_agent_phone}}` + - **Message**: "Connecting you with our billing department." + - **Transfer Plan**: Include account standing and payment history + + + Each transfer node should include rich context: + + ```txt title="Transfer Plan Summary" + Tenant: {{tenant_name}} at {{property_address}} + Account Status: {{account_standing}} + Inquiry Type: {{inquiry_type}} + Previous Context: {{conversation_summary}} + Priority: {{priority_level}} + ``` + + + +--- + +## 7. Add Error Handling and Fallbacks + + + + Add **API Request** node for fallback scenarios: + + **Triggers**: + - API lookup failures + - No available agents + - Unknown inquiry types + - System errors + + **URL**: `https://your-system.com/api/routing/fallback` + **Body**: + ```json + { + "phone": "{{caller_phone}}", + "error_type": "{{error_reason}}", + "original_inquiry": "{{inquiry_type}}" + } + ``` + + + Create **Transfer Call** node for general queue: + + **Destination**: Main office line + **Message**: "Let me connect you with our general team who can assist you." + **Transfer Plan**: "Call requires general assistance - routing details unavailable" + + + Add **End Call** node with voicemail message: + + **Condition**: All agents busy and caller declines callback + **Message**: "Please leave a detailed voicemail including your name, property address, and the nature of your request. We'll call you back within 4 hours." + + + +--- + +## 8. Test Your Property Routing Workflow + + + + + + - Navigate to **Phone Numbers** in your dashboard + - Click **Create Phone Number** + - Assign your property management workflow + - Configure any additional settings + + + Call your number and test various scenarios: + - Emergency maintenance calls + - Regular maintenance requests from verified tenants + - Leasing inquiries from prospective tenants + - Billing questions from current tenants + - Calls from unrecognized phone numbers + + + Check your API logs to verify: + - Tenant lookup requests and responses + - Agent availability checks + - Routing decision API calls + - Callback scheduling if needed + + + + + ```typescript + import { VapiClient } from "@vapi-ai/server-sdk"; + + const vapi = new VapiClient({ token: process.env.VAPI_API_KEY }); + + async function testPropertyRouting(workflowId: string) { + try { + // Test emergency maintenance call + const emergencyCall = await vapi.calls.create({ + workflowId: workflowId, + customer: { + number: "+1234567890", // Known tenant number + name: "Emergency Maintenance Test" + } + }); + + console.log(`Emergency test call created: ${emergencyCall.id}`); + + // Test general leasing inquiry + const leasingCall = await vapi.calls.create({ + workflowId: workflowId, + customer: { + number: "+0987654321", // Unknown number + name: "Leasing Inquiry Test" + } + }); + + console.log(`Leasing test call created: ${leasingCall.id}`); + + return { emergencyCall, leasingCall }; + } catch (error) { + console.error('Error creating test calls:', error); + throw error; + } + } + + // Test the property routing workflow + const testCalls = await testPropertyRouting('YOUR_WORKFLOW_ID'); + ``` + + + ```python + import requests + import os + + def test_property_routing(workflow_id): + """Test property management routing with different scenarios""" + url = "https://api.vapi.ai/call" + headers = { + "Authorization": f"Bearer {os.getenv('VAPI_API_KEY')}", + "Content-Type": "application/json" + } + + test_scenarios = [ + { + "name": "Emergency Maintenance", + "customer": { + "number": "+1234567890", # Known tenant + "name": "Emergency Maintenance Test" + } + }, + { + "name": "General Leasing Inquiry", + "customer": { + "number": "+0987654321", # Unknown number + "name": "Leasing Inquiry Test" + } + }, + { + "name": "Billing Question", + "customer": { + "number": "+1111111111", # Known tenant + "name": "Billing Question Test" + } + } + ] + + results = [] + for scenario in test_scenarios: + try: + data = { + "workflowId": workflow_id, + **scenario + } + + response = requests.post(url, headers=headers, json=data) + response.raise_for_status() + call = response.json() + + print(f"{scenario['name']} call created: {call['id']}") + results.append(call) + + except requests.exceptions.RequestException as error: + print(f"Error creating {scenario['name']}: {error}") + + return results + + # Test the property routing workflow + test_calls = test_property_routing('YOUR_WORKFLOW_ID') + ``` + + + +## API Integration Examples + +### Tenant Lookup API + +```typescript +// Example tenant lookup API for property management +app.post('/api/tenants/lookup', async (req, res) => { + try { + const { phone, inquiry_type } = req.body; + + // Look up tenant by phone number + const tenant = await propertyDB.query(` + SELECT t.id, t.name, t.status, p.address, a.standing, a.balance + FROM tenants t + JOIN properties p ON t.property_id = p.id + JOIN accounts a ON t.account_id = a.id + WHERE t.phone = ? + `, [phone]); + + if (!tenant.length) { + return res.json({ + tenant: null, + routing_suggestion: "general_queue" + }); + } + + const tenantData = tenant[0]; + + res.json({ + tenant: { + id: tenantData.id, + name: tenantData.name, + status: tenantData.status, + property: tenantData.address, + account_standing: tenantData.standing, + balance: tenantData.balance + }, + routing_suggestion: determineRoutingSuggestion(tenantData, inquiry_type) + }); + } catch (error) { + console.error('Tenant lookup error:', error); + res.status(500).json({ error: 'Lookup failed' }); + } +}); +``` + +### Agent Availability Check + +```typescript +// Example agent availability API +app.get('/api/agents/availability', async (req, res) => { + try { + const { department, priority } = req.query; + + const availableAgents = await agentDB.query(` + SELECT a.id, a.name, a.phone, a.current_calls, a.department + FROM agents a + WHERE a.department = ? + AND a.status = 'available' + AND a.current_calls < a.max_calls + ORDER BY a.current_calls ASC, a.priority_level DESC + `, [department]); + + const queueLength = await getQueueLength(department); + const estimatedWait = calculateEstimatedWait(queueLength, availableAgents.length); + + res.json({ + available_agents: availableAgents, + queue_length: queueLength, + estimated_wait_minutes: estimatedWait, + department_status: availableAgents.length > 0 ? 'available' : 'busy' + }); + } catch (error) { + console.error('Availability check error:', error); + res.status(500).json({ error: 'Availability check failed' }); + } +}); +``` + +### Emergency Routing Logic + +```typescript +// Example emergency routing API +app.post('/api/routing/emergency', async (req, res) => { + try { + const { tenant_id, property, inquiry_type } = req.body; + + // Get emergency contact based on property location + const emergencyTeam = await getEmergencyTeam(property); + + // Create emergency ticket + const ticket = await createEmergencyTicket({ + tenant_id, + property, + priority: 'critical', + source: 'phone' + }); + + // Get first available emergency agent + const agent = emergencyTeam.find(agent => agent.available) || emergencyTeam[0]; + + res.json({ + destination: agent.phone, + agent_name: agent.name, + ticket_id: ticket.id, + priority: 'critical', + transfer_message: `Emergency maintenance call for ${property}. Ticket #${ticket.id} created.` + }); + } catch (error) { + console.error('Emergency routing error:', error); + res.status(500).json({ error: 'Emergency routing failed' }); + } +}); +``` + +### Callback Scheduling + +```typescript +// Example callback scheduling API +app.post('/api/callbacks/schedule', async (req, res) => { + try { + const { tenant_id, phone, inquiry_type, priority, requested_time } = req.body; + + // Schedule callback in system + const callback = await callbackDB.create({ + tenant_id, + phone, + inquiry_type, + priority, + requested_time: requested_time || getNextAvailableSlot(), + status: 'scheduled', + created_at: new Date() + }); + + // Send confirmation SMS/email + await sendCallbackConfirmation(phone, callback.scheduled_time); + + res.json({ + callback_id: callback.id, + scheduled_time: callback.scheduled_time, + confirmation_sent: true, + message: `Callback scheduled for ${formatTime(callback.scheduled_time)}` + }); + } catch (error) { + console.error('Callback scheduling error:', error); + res.status(500).json({ error: 'Callback scheduling failed' }); + } +}); +``` + +## Advanced Workflow Features + +### Queue Management with Priorities + +```typescript +function determineRoutingPriority(tenant: any, inquiryType: string) { + // Enterprise/commercial tenants get priority + if (tenant.tier === 'commercial') return 'high'; + + // Emergency situations + if (inquiryType === 'emergency') return 'critical'; + + // Account issues for good standing tenants + if (inquiryType === 'rent' && tenant.account_standing === 'good') return 'normal'; + + // Delinquent accounts get lower priority for non-urgent matters + if (tenant.account_standing === 'delinquent' && inquiryType !== 'emergency') return 'low'; + + return 'normal'; +} +``` + +### Business Hours Routing + +```typescript +function getRoutingDestination(inquiry: any, currentTime: Date) { + const businessHours = isBusinessHours(currentTime); + + if (!businessHours) { + // After hours emergency routing + if (inquiry.type === 'emergency') { + return { + destination: process.env.EMERGENCY_LINE, + message: "Connecting you to our emergency maintenance line." + }; + } + + // After hours general routing + return { + destination: process.env.VOICEMAIL_LINE, + message: "Our office is currently closed. Please leave a detailed message." + }; + } + + // Business hours routing logic + return getBusinessHoursDestination(inquiry); +} +``` + +## Next Steps + +You've built a sophisticated property management call routing workflow! Consider these enhancements: + +* **[Customer support escalation system](/assistants/examples/support-escalation)** - Explore the assistant-based approach +* **[Workflow Analytics](/workflows/analytics)** - Track routing patterns and optimize decision trees +* **[Integration Templates](/workflows/integrations)** - Connect with popular property management systems +* **[Advanced Routing](/workflows/advanced-routing)** - Implement complex routing logic with multiple conditions