This workflow corresponds to n8n.io template #13705 — we link there as the canonical source.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "9708565d-ec6a-4933-96a1-75998a3f62b7",
"name": "\ud83d\udccb Main Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
-48
],
"parameters": {
"width": 580,
"height": 488,
"content": "## \ud83c\udfe0 Tenant Support System \u2013 Maintenance Request Tracker\n\n**Tenant journey in 4 steps:**\n\n\u2460 Tenant WhatsApps an issue \u2192 ticket auto-created in Google Sheets\n\u2461 Ticket assigned to right maintenance team based on category\n\u2462 Maintenance team updates status \u2192 tenant gets notified automatically\n\u2463 Tenant rates the service \u2192 feedback logged for landlord dashboard\n\n**Two entry points:**\n- **WATI Trigger** \u2192 handles all tenant & maintenance replies\n- **Schedule Trigger** \u2192 daily SLA breach check & overdue alerts\n\n**Credentials:** WATI + Google Sheets OAuth2 only"
},
"typeVersion": 1
},
{
"id": "85564ffc-a450-4d3c-9466-1bbaaab885e4",
"name": "Sticky \u2013 Ticket Creation",
"type": "n8n-nodes-base.stickyNote",
"position": [
1200,
32
],
"parameters": {
"color": 7,
"width": 868,
"height": 616,
"content": "### \ud83c\udfab Ticket Creation Path\nTenant texts any issue \u2192 **Classify Issue Code** uses keyword matching to auto-detect category (Plumbing / Electrical / Appliance / Security / Other) and priority (High / Medium / Low) \u2192 **Sheets Append** creates ticket with unique ID \u2192 Tenant gets ticket confirmation AND maintenance team is notified simultaneously."
},
"typeVersion": 1
},
{
"id": "9930a9c6-cf6e-42fa-85df-edce3612876e",
"name": "Sticky \u2013 Status Updates",
"type": "n8n-nodes-base.stickyNote",
"position": [
1200,
720
],
"parameters": {
"color": 7,
"width": 988,
"height": 488,
"content": "### \ud83d\udd01 Status Update Path\nMaintenance team replies with *update <ticket-id> <status> <note>* \u2192 **Parse Update Code** extracts fields \u2192 **Sheets Update** changes ticket status \u2192 Two WATI messages sent: ack to team + status card to tenant.\n\nValid statuses: `assigned` `in-progress` `resolved` `closed`"
},
"typeVersion": 1
},
{
"id": "e26e0843-9fb8-43cc-9802-6a893660c073",
"name": "Sticky \u2013 Feedback & Reports",
"type": "n8n-nodes-base.stickyNote",
"position": [
1232,
1248
],
"parameters": {
"color": 7,
"width": 908,
"height": 648,
"content": "### \u2b50 Feedback & Landlord Dashboard\nWhen ticket marked *resolved* \u2192 tenant receives a rating prompt \u2192 tenant replies *rate <ticket-id> <1-5>* \u2192 rating logged in Sheets.\n\nLandlord texts *dashboard* \u2192 gets full property overview: open tickets, SLA breaches, avg rating, category breakdown."
},
"typeVersion": 1
},
{
"id": "12a863c5-8bac-4a24-8a3f-4e79b8b6146b",
"name": "Sticky \u2013 SLA Monitor",
"type": "n8n-nodes-base.stickyNote",
"position": [
2400,
864
],
"parameters": {
"color": 2,
"width": 1160,
"height": 440,
"content": "### \ud83d\udea8 SLA Breach Monitor\n**Schedule Trigger** runs every morning at 8AM.\nReads all open tickets \u2192 flags any ticket older than the SLA threshold per priority:\n- \ud83d\udd34 High \u2192 4 hours\n- \ud83d\udfe1 Medium \u2192 24 hours\n- \ud83d\udfe2 Low \u2192 72 hours\n\nSends breach report to landlord AND pings each maintenance team for their overdue tickets."
},
"typeVersion": 1
},
{
"id": "e910d4bb-a1d5-4303-8cb4-ab6b9fc88ed3",
"name": "Master Router",
"type": "n8n-nodes-base.switch",
"position": [
624,
880
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "Status Update",
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "773beeb3-125d-4177-b48e-0c3676871761",
"operator": {
"type": "string",
"operation": "startsWith"
},
"leftValue": "={{ $json.text }}",
"rightValue": "update "
}
]
},
"renameOutput": true
},
{
"outputKey": "Rate Ticket",
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "69f1d90f-a51f-4c90-9b8c-04932b1135c2",
"operator": {
"type": "string",
"operation": "startsWith"
},
"leftValue": "={{ $json.text }}",
"rightValue": "rate "
}
]
},
"renameOutput": true
},
{
"outputKey": "My Tickets",
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "55521424-4afb-4241-b890-5d49cd250a0a",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.text.toLowerCase().trim() }}",
"rightValue": "mystatus"
}
]
},
"renameOutput": true
},
{
"outputKey": "Dashboard",
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a3569247-bdc7-49e6-b192-e79f6d8b73f1",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.text.toLowerCase().trim() }}",
"rightValue": "dashboard"
}
]
},
"renameOutput": true
}
]
},
"options": {
"fallbackOutput": "extra"
}
},
"typeVersion": 3
},
{
"id": "fd7fcf42-47e0-4662-9771-f4c30ee4bb83",
"name": "Classify Issue & Create Ticket",
"type": "n8n-nodes-base.code",
"position": [
1264,
352
],
"parameters": {
"jsCode": "// Classify Issue & Create Ticket\n// Auto-detects category and priority from the tenant's message\n\nconst text = ($json.text || '').toLowerCase().trim();\nconst phone = $json.waId || $json.from || 'unknown';\nconst tenantName = $json.senderName || 'Tenant';\nconst now = new Date();\n\nconst categories = {\n Plumbing: ['leak', 'pipe', 'water', 'tap', 'drain', 'toilet', 'flush', 'sink', 'shower', 'sewage'],\n Electrical: ['light', 'power', 'electricity', 'switch', 'socket', 'outlet', 'fuse', 'wiring', 'fan', 'ac', 'air condition'],\n Appliance: ['fridge', 'washing machine', 'oven', 'microwave', 'dishwasher', 'heater', 'geyser', 'appliance'],\n Security: ['lock', 'door', 'window', 'cctv', 'camera', 'key', 'gate', 'security', 'break', 'stolen'],\n Structural: ['wall', 'ceiling', 'floor', 'crack', 'roof', 'paint', 'damp', 'mold', 'mould', 'termite'],\n Other: []\n};\n\nlet detectedCategory = 'Other';\nfor (const [cat, keywords] of Object.entries(categories)) {\n if (keywords.some(k => text.includes(k))) { detectedCategory = cat; break; }\n}\n\nconst highPriorityWords = ['emergency', 'urgent', 'flood', 'fire', 'gas leak', 'no power', 'burst', 'danger', 'break-in'];\nconst lowPriorityWords = ['minor', 'small', 'whenever', 'no rush', 'paint', 'cosmetic'];\n\nlet priority = 'Medium';\nif (highPriorityWords.some(w => text.includes(w))) priority = 'High';\nelse if (lowPriorityWords.some(w => text.includes(w))) priority = 'Low';\n\nconst slaHours = { High: 4, Medium: 24, Low: 72 };\nconst slaDue = new Date(now.getTime() + slaHours[priority] * 60 * 60 * 1000);\n\nconst datePart = now.toISOString().split('T')[0].replace(/-/g, '');\nconst randPart = Math.random().toString(36).substring(2, 6).toUpperCase();\nconst ticketId = `TKT-${datePart}-${randPart}`;\n\nconst teamMap = {\n Plumbing: { name: 'Plumbing Team', phone: 917727827113 },\n Electrical: { name: 'Electrical Team', phone: '917024935915' },\n Appliance: { name: 'Appliance Team', phone: 'APPLIANCE_TEAM_PHONE' },\n Security: { name: 'Security Team', phone: 'SECURITY_TEAM_PHONE' },\n Structural: { name: 'Structural Team', phone: 'STRUCTURAL_TEAM_PHONE' },\n Other: { name: 'General Team', phone: 'GENERAL_TEAM_PHONE' }\n};\n\nconst assignedTeam = teamMap[detectedCategory];\nconst priorityEmoji = { High: '\\u{1F534}', Medium: '\\u{1F7E1}', Low: '\\u{1F7E2}' }[priority];\n\nconst tenantMsg = [\n `\\uD83C\\uDFAB *Ticket Created Successfully!*`,\n '',\n `\\uD83D\\uDCCB *Ticket ID:* \\`${ticketId}\\``,\n `\\uD83C\\uDFF7\\uFE0F *Category:* ${detectedCategory}`,\n `${priorityEmoji} *Priority:* ${priority}`,\n `\\uD83D\\uDCDD *Issue:* ${$json.text}`,\n `\\uD83D\\uDD50 *Logged At:* ${now.toLocaleString('en-IN')}`,\n `\\u23F0 *Expected Resolution:* Within ${slaHours[priority]} hours`,\n '',\n 'Our team has been notified and will be in touch shortly.',\n `Save your Ticket ID *${ticketId}* for future reference.`,\n '',\n 'Reply *mystatus* anytime to check your open tickets.'\n].join('\\n');\n\nconst teamMsg = [\n `\\uD83D\\uDD14 *New Maintenance Ticket*`,\n '',\n `\\uD83C\\uDFAB *Ticket ID:* ${ticketId}`,\n `${priorityEmoji} *Priority:* ${priority}`,\n `\\uD83C\\uDFF7\\uFE0F *Category:* ${detectedCategory}`,\n `\\uD83D\\uDC64 *Tenant:* ${tenantName}`,\n `\\uD83D\\uDCDE *Phone:* ${phone}`,\n `\\uD83D\\uDCDD *Issue:* ${$json.text}`,\n `\\u23F0 *SLA Deadline:* ${slaDue.toLocaleString('en-IN')}`,\n '',\n 'To update status, reply:',\n `*update ${ticketId} in-progress Your note here*`\n].join('\\n');\n\nreturn { json: {\n ticketId, phone, tenantName,\n issueText: $json.text,\n category: detectedCategory,\n priority,\n status: 'Open',\n assignedTeam: assignedTeam.name,\n teamPhone: assignedTeam.phone,\n createdAt: now.toISOString(),\n slaDueAt: slaDue.toISOString(),\n rating: '',\n tenantMsg,\n teamMsg\n}};"
},
"typeVersion": 2
},
{
"id": "837321fe-bcd5-43bf-9c2b-56caf5169e94",
"name": "Google Sheets \u2013 Create Ticket",
"type": "n8n-nodes-base.googleSheets",
"position": [
1504,
352
],
"parameters": {
"columns": {
"value": {
"phone": "={{ $json.phone }}",
"status": "Open",
"category": "={{ $json.category }}",
"priority": "={{ $json.priority }}",
"slaDueAt": "={{ $json.slaDueAt }}",
"ticketId": "={{ $json.ticketId }}",
"createdAt": "={{ $json.createdAt }}",
"issueText": "={{ $json.issueText }}",
"teamPhone": "={{ $json.teamPhone }}",
"tenantName": "={{ $json.tenantName }}",
"lastUpdated": "={{ $json.createdAt }}",
"assignedTeam": "={{ $json.assignedTeam }}"
},
"schema": [
{
"id": "ticketId",
"type": "string",
"display": true,
"required": false,
"displayName": "ticketId",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "phone",
"type": "string",
"display": true,
"required": false,
"displayName": "phone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "tenantName",
"type": "string",
"display": true,
"required": false,
"displayName": "tenantName",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "issueText",
"type": "string",
"display": true,
"required": false,
"displayName": "issueText",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "category",
"type": "string",
"display": true,
"required": false,
"displayName": "category",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "priority",
"type": "string",
"display": true,
"required": false,
"displayName": "priority",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "status",
"type": "string",
"display": true,
"required": false,
"displayName": "status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "assignedTeam",
"type": "string",
"display": true,
"required": false,
"displayName": "assignedTeam",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "teamPhone",
"type": "string",
"display": true,
"required": false,
"displayName": "teamPhone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "createdAt",
"type": "string",
"display": true,
"required": false,
"displayName": "createdAt",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "slaDueAt",
"type": "string",
"display": true,
"required": false,
"displayName": "slaDueAt",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "rating",
"type": "string",
"display": true,
"required": false,
"displayName": "rating",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "lastUpdated",
"type": "string",
"display": true,
"required": false,
"displayName": "lastUpdated",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "notes",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "notes",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1F7rsFG0c-ORJPf9t0hb2nwSMkXqxtyAp_5IyIV4rkdc/edit#gid=0",
"cachedResultName": "Tickets"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1F7rsFG0c-ORJPf9t0hb2nwSMkXqxtyAp_5IyIV4rkdc",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1F7rsFG0c-ORJPf9t0hb2nwSMkXqxtyAp_5IyIV4rkdc/edit?usp=drivesdk",
"cachedResultName": "Untitled spreadsheet"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "33807f78-b11b-4ef0-9878-4b98ce3d8c0d",
"name": "WATI \u2013 Confirm to Tenant",
"type": "n8n-nodes-wati.wati",
"position": [
1712,
224
],
"parameters": {
"target": "={{ $('Classify Issue & Create Ticket').item.json.phone }}",
"messageText": "={{ $('Classify Issue & Create Ticket').item.json.tenantMsg }}"
},
"credentials": {
"watiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "0da128fb-8b49-464b-a600-a57f9f0bbfa5",
"name": "WATI \u2013 Notify Maintenance Team",
"type": "n8n-nodes-wati.wati",
"position": [
1712,
416
],
"parameters": {
"target": "=917024935915",
"messageText": "={{ $('Classify Issue & Create Ticket').item.json.teamMsg }}"
},
"credentials": {
"watiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "ddd1b7ad-9af2-4299-b79e-d1d1341299dd",
"name": "Google Sheets \u2013 Read Tickets (Update)",
"type": "n8n-nodes-base.googleSheets",
"position": [
1232,
928
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1F7rsFG0c-ORJPf9t0hb2nwSMkXqxtyAp_5IyIV4rkdc/edit#gid=0",
"cachedResultName": "Tickets"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1F7rsFG0c-ORJPf9t0hb2nwSMkXqxtyAp_5IyIV4rkdc",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1F7rsFG0c-ORJPf9t0hb2nwSMkXqxtyAp_5IyIV4rkdc/edit?usp=drivesdk",
"cachedResultName": "Untitled spreadsheet"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "a4975e18-e634-4293-81a3-e7facc774ba6",
"name": "Parse Status Update",
"type": "n8n-nodes-base.code",
"position": [
1456,
928
],
"parameters": {
"jsCode": "// Parse Status Update from Maintenance Team\n// Format: 'update TKT-20240225-ABCD in-progress Technician arriving at 3PM'\n\nconst text = ($('Wati Trigger').item.json.text || '').trim();\nconst updaterPhone = $('Wati Trigger').item.json.waId || $('Wati Trigger').item.json.from;\n\nconst parts = text.replace(/^update\\s+/i, '').split(' ');\nconst ticketId = (parts[0] || '').toUpperCase();\nconst newStatus = (parts[1] || '').toLowerCase();\nconst note = parts.slice(2).join(' ') || '';\n\nconst validStatuses = ['assigned', 'in-progress', 'resolved', 'closed'];\nif (!ticketId || !validStatuses.includes(newStatus)) {\n return [{ json: {\n updaterPhone,\n errorMessage: `\\u26A0\\uFE0F Invalid format. Use:\\n*update <TICKET-ID> <status> <note>*\\n\\nValid statuses: ${validStatuses.join(', ')}\\n\\nExample:\\n*update TKT-20240225-ABCD in-progress Technician on the way*`,\n hasError: true\n }}];\n}\n\nconst allRows = $input.all();\nconst ticketRow = allRows.find(r => (r.json.ticketId || '').toUpperCase() === ticketId);\n\nif (!ticketRow) {\n return [{ json: {\n updaterPhone,\n errorMessage: `\\u274C Ticket *${ticketId}* not found. Please check the ticket ID.`,\n hasError: true\n }}];\n}\n\nconst ticket = ticketRow.json;\nconst now = new Date();\nconst priorityEmoji = { High: '\\uD83D\\uDD34', Medium: '\\uD83D\\uDFE1', Low: '\\uD83D\\uDFE2' }[ticket.priority] || '\\u26AA';\nconst statusEmoji = { assigned: '\\uD83D\\uDC77', 'in-progress': '\\uD83D\\uDD27', resolved: '\\u2705', closed: '\\uD83D\\uDD12' }[newStatus] || '\\uD83D\\uDCCB';\n\nconst ackMessage = [\n `${statusEmoji} *Ticket Updated*`,\n `\\uD83C\\uDFAB ${ticketId} \\u2192 *${newStatus.toUpperCase()}*`,\n note ? `\\uD83D\\uDCDD Note: ${note}` : '',\n `\\uD83D\\uDD50 Updated at ${now.toLocaleTimeString('en-IN', {hour:'2-digit', minute:'2-digit'})}`\n].filter(Boolean).join('\\n');\n\nconst tenantLines = [\n `${statusEmoji} *Ticket Update \\u2013 ${ticketId}*`,\n '',\n 'Your maintenance request has been updated:',\n '',\n `\\uD83D\\uDCCB *Status:* ${newStatus.toUpperCase()}`,\n `\\uD83C\\uDFF7\\uFE0F *Category:* ${ticket.category}`,\n `${priorityEmoji} *Priority:* ${ticket.priority}`,\n note ? `\\uD83D\\uDCDD *Note from team:* ${note}` : '',\n `\\uD83D\\uDD50 *Updated:* ${now.toLocaleString('en-IN')}`\n].filter(Boolean);\n\nif (newStatus === 'resolved') {\n tenantLines.push('');\n tenantLines.push('\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501');\n tenantLines.push('\\u2B50 *How did we do?*');\n tenantLines.push(`Reply: *rate ${ticketId} <1-5>*`);\n tenantLines.push(`Example: *rate ${ticketId} 5*`);\n} else {\n tenantLines.push('');\n tenantLines.push('Reply *mystatus* to see all your tickets.');\n}\n\nreturn [{ json: {\n ticketId,\n tenantPhone: ticket.phone,\n updaterPhone,\n newStatus, note,\n updatedAt: now.toISOString(),\n ackMessage,\n tenantMessage: tenantLines.join('\\n'),\n isResolved: newStatus === 'resolved',\n hasError: false\n}}];"
},
"typeVersion": 2
},
{
"id": "7dd2214e-71a4-4e8b-a94f-ea018d229bc6",
"name": "Google Sheets \u2013 Update Ticket Status",
"type": "n8n-nodes-base.googleSheets",
"position": [
1696,
928
],
"parameters": {
"columns": {
"value": {
"notes": "={{ $node[\"Parse Status Update\"].json.note || \"\" }}",
"status": "={{ $node[\"Google Sheets \u2013 Read Tickets (Update)\"].json.status }}",
"ticketId": "={{ $items(\"Google Sheets \u2013 Read Tickets (Update)\")[0].json.ticketId }}",
"row_number": 0,
"lastUpdated": "={{ $node[\"Google Sheets \u2013 Read Tickets (Update)\"].json.lastUpdated }}"
},
"schema": [
{
"id": "ticketId",
"type": "string",
"display": true,
"required": false,
"displayName": "ticketId",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "phone",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "phone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "tenantName",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "tenantName",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "issueText",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "issueText",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "category",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "category",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "priority",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "priority",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "status",
"type": "string",
"display": true,
"required": false,
"displayName": "status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "assignedTeam",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "assignedTeam",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "teamPhone",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "teamPhone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "createdAt",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "createdAt",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "slaDueAt",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "slaDueAt",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "rating",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "rating",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "lastUpdated",
"type": "string",
"display": true,
"required": false,
"displayName": "lastUpdated",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "notes",
"type": "string",
"display": true,
"required": false,
"displayName": "notes",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "number",
"display": true,
"removed": false,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"ticketId"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1F7rsFG0c-ORJPf9t0hb2nwSMkXqxtyAp_5IyIV4rkdc/edit#gid=0",
"cachedResultName": "Tickets"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1F7rsFG0c-ORJPf9t0hb2nwSMkXqxtyAp_5IyIV4rkdc",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1F7rsFG0c-ORJPf9t0hb2nwSMkXqxtyAp_5IyIV4rkdc/edit?usp=drivesdk",
"cachedResultName": "Untitled spreadsheet"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "85ff89e9-cdda-4ae4-be5c-f328124b919a",
"name": "WATI \u2013 Acknowledge Team",
"type": "n8n-nodes-wati.wati",
"position": [
1920,
832
],
"parameters": {
"target": "={{ $('Parse Status Update').item.json.updaterPhone }}",
"messageText": "=ji"
},
"credentials": {
"watiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "45bcbe7a-80b6-46d8-9b76-5663d4f0c52f",
"name": "WATI \u2013 Notify Tenant of Update",
"type": "n8n-nodes-wati.wati",
"position": [
1920,
1008
],
"parameters": {
"target": "={{ $items(\"Wati Trigger\")[0].json.waId }}",
"messageText": "={{ $('Parse Status Update').item.json.errorMessage }}"
},
"credentials": {
"watiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "13bd3e03-fd28-41c2-bfe3-f1fe5251b0d1",
"name": "Parse Rating",
"type": "n8n-nodes-base.code",
"position": [
1312,
1376
],
"parameters": {
"jsCode": "// Parse & Log Rating\n// Format: 'rate TKT-20240225-ABCD 5'\n\nconst text = ($('Wati Trigger').item.json.text || '').trim();\nconst phone = $('Wati Trigger').item.json.waId || $('Wati Trigger').item.json.from;\nconst tenantName = $('Wati Trigger').item.json.senderName || 'Tenant';\n\nconst parts = text.replace(/^rate\\s+/i, '').split(' ');\nconst ticketId = (parts[0] || '').toUpperCase();\nconst rating = parseInt(parts[1] || '0');\n\nif (!ticketId || isNaN(rating) || rating < 1 || rating > 5) {\n return [{ json: {\n phone,\n ratingMessage: `\\u26A0\\uFE0F Invalid format. Use:\\n*rate <TICKET-ID> <1-5>*\\n\\nExample: *rate TKT-20240225-ABCD 5*`,\n hasError: true\n }}];\n}\n\nconst stars = '\\u2B50'.repeat(rating) + '\\u2606'.repeat(5 - rating);\nconst feedbackMap = { 5: 'Excellent!', 4: 'Good!', 3: 'Average', 2: 'Below expectations', 1: 'Poor \\u2013 we will improve' };\n\nconst ratingLines = [\n `${stars} *Thank you for your feedback!*`,\n '',\n `You rated ticket *${ticketId}*: *${rating}/5 \\u2013 ${feedbackMap[rating]}*`,\n '',\n 'Your feedback helps us improve our service.',\n 'For any new issues, just send us a message! \\uD83C\\uDFE0'\n];\n\nreturn [{ json: {\n phone, ticketId, rating,\n ratingMessage: ratingLines.join('\\n'),\n ratedAt: new Date().toISOString(),\n hasError: false\n}}];"
},
"typeVersion": 2
},
{
"id": "9cb7e77f-8db2-4948-87e3-43020648b909",
"name": "Google Sheets \u2013 Save Rating",
"type": "n8n-nodes-base.googleSheets",
"position": [
1552,
1376
],
"parameters": {
"columns": {
"value": {
"rating": "={{ $json.rating }}",
"ticketId": 0,
"lastUpdated": "={{ $json.ratedAt }}"
},
"schema": [
{
"id": "ticketId",
"type": "number",
"display": true,
"required": true,
"displayName": "ticketId",
"defaultMatch": true,
"canBeUsedToMatch": true
},
{
"id": "rating",
"type": "number",
"display": true,
"required": false,
"displayName": "rating",
"defaultMatch": false,
"canBeUsedToMatch": false
},
{
"id": "lastUpdated",
"type": "string",
"display": true,
"required": false,
"displayName": "lastUpdated",
"defaultMatch": false,
"canBeUsedToMatch": false
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"ticketId"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1F7rsFG0c-ORJPf9t0hb2nwSMkXqxtyAp_5IyIV4rkdc/edit#gid=0",
"cachedResultName": "Tickets"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1F7rsFG0c-ORJPf9t0hb2nwSMkXqxtyAp_5IyIV4rkdc",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1F7rsFG0c-ORJPf9t0hb2nwSMkXqxtyAp_5IyIV4rkdc/edit?usp=drivesdk",
"cachedResultName": "Untitled spreadsheet"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "75f17e80-623e-477d-8e36-ecc6db4f2cdc",
"name": "WATI \u2013 Send Rating Thanks",
"type": "n8n-nodes-wati.wati",
"position": [
1840,
1376
],
"parameters": {
"target": "={{ $('Parse Rating').item.json.phone }}",
"messageText": "={{ $('Parse Rating').item.json.ratingMessage }}"
},
"credentials": {
"watiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "81203671-ef43-4218-89ca-bd91ad5c7f14",
"name": "Google Sheets \u2013 Read My Tickets",
"type": "n8n-nodes-base.googleSheets",
"position": [
1312,
1536
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1F7rsFG0c-ORJPf9t0hb2nwSMkXqxtyAp_5IyIV4rkdc/edit#gid=0",
"cachedResultName": "Tickets"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1F7rsFG0c-ORJPf9t0hb2nwSMkXqxtyAp_5IyIV4rkdc",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1F7rsFG0c-ORJPf9t0hb2nwSMkXqxtyAp_5IyIV4rkdc/edit?usp=drivesdk",
"cachedResultName": "Untitled spreadsheet"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "dee9ba78-1d4b-4bfd-87c0-bd0059d292b0",
"name": "Build My Tickets View",
"type": "n8n-nodes-base.code",
"position": [
1552,
1536
],
"parameters": {
"jsCode": "// Build My Tickets Status Card\n\nconst phone = $('Wati Trigger').item.json.waId || $('Wati Trigger').item.json.from;\nconst tenantName = $('Wati Trigger').item.json.senderName || 'Tenant';\nconst allRows = $input.all();\n\nconst myTickets = allRows\n .filter(r => r.json.phone === phone)\n .sort((a, b) => new Date(b.json.createdAt) - new Date(a.json.createdAt));\n\nif (myTickets.length === 0) {\n return [{ json: {\n phone,\n myTicketsMessage: `\\uD83D\\uDCCB *No tickets found, ${tenantName}!*\\n\\nTo report a maintenance issue, just describe your problem in a message.\\nExample: *The kitchen tap is leaking*`\n }}];\n}\n\nconst open = myTickets.filter(r => !['resolved','closed'].includes((r.json.status||'').toLowerCase()));\nconst closed = myTickets.filter(r => ['resolved','closed'].includes((r.json.status||'').toLowerCase())).slice(0, 3);\n\nconst statusEmoji = { open: '\\uD83D\\uDFE0', assigned: '\\uD83D\\uDC77', 'in-progress': '\\uD83D\\uDD27', resolved: '\\u2705', closed: '\\uD83D\\uDD12' };\nconst priorityEmoji = { High: '\\uD83D\\uDD34', Medium: '\\uD83D\\uDFE1', Low: '\\uD83D\\uDFE2' };\n\nconst lines = [`\\uD83D\\uDCCB *My Maintenance Tickets*`, `\\uD83D\\uDC64 *${tenantName}*`, ''];\n\nif (open.length > 0) {\n lines.push(`*Open Tickets (${open.length}):*`);\n for (const r of open) {\n const se = statusEmoji[(r.json.status||'open').toLowerCase()] || '\\uD83D\\uDCCB';\n const pe = priorityEmoji[r.json.priority] || '\\u26AA';\n let created = r.json.createdAt;\n try { created = new Date(r.json.createdAt).toLocaleDateString('en-IN', { day:'numeric', month:'short' }); } catch(e) {}\n lines.push(`${se} *${r.json.ticketId}* ${pe} ${r.json.priority}`);\n lines.push(` ${r.json.category} | ${r.json.status || 'Open'}`);\n lines.push(` \\uD83D\\uDCC5 Raised: ${created}`);\n if (r.json.notes) lines.push(` \\uD83D\\uDCDD ${r.json.notes}`);\n lines.push('');\n }\n}\n\nif (closed.length > 0) {\n lines.push(`*Recently Closed (${closed.length}):*`);\n for (const r of closed) {\n const rating = r.json.rating ? `\\u2B50 ${r.json.rating}/5` : 'Not rated';\n lines.push(`\\u2705 ${r.json.ticketId} \\u2013 ${r.json.category} | ${rating}`);\n }\n lines.push('');\n}\n\nlines.push('\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501');\nlines.push('To report a new issue, just describe it in a message.');\n\nreturn [{ json: { phone, myTicketsMessage: lines.join('\\n') } }];"
},
"typeVersion": 2
},
{
"id": "aea2f273-5455-4895-801d-14156d4cea68",
"name": "WATI \u2013 Send My Tickets",
"type": "n8n-nodes-wati.wati",
"position": [
1824,
1536
],
"parameters": {
"target": "={{ $('Build My Tickets View').item.json.phone }}",
"messageText": "={{ $('Build My Tickets View').item.json.myTicketsMessage }}"
},
"credentials": {
"watiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "646cd7f6-7320-49fe-b260-48d760ed0986",
"name": "Google Sheets \u2013 Read All Tickets (Dashboard)",
"type": "n8n-nodes-base.googleSheets",
"position": [
1312,
1696
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1F7rsFG0c-ORJPf9t0hb2nwSMkXqxtyAp_5IyIV4rkdc/edit#gid=0",
"cachedResultName": "Tickets"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1F7rsFG0c-ORJPf9t0hb2nwSMkXqxtyAp_5IyIV4rkdc",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1F7rsFG0c-ORJPf9t0hb2nwSMkXqxtyAp_5IyIV4rkdc/edit?usp=drivesdk",
"cachedResultName": "Untitled spreadsheet"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "11af040b-24f2-45ff-83f1-79340bfd0fec",
"name": "Build Landlord Dashboard",
"type": "n8n-nodes-base.code",
"position": [
1552,
1696
],
"parameters": {
"jsCode": "// Build Landlord Dashboard\n\nconst allRows = $input.all();\nconst now = new Date();\nconst requesterPhone = $('Wati Trigger').item.json.waId || $('Wati Trigger').item.json.from;\n\nconst allTickets = allRows.map(r => r.json);\nconst openTickets = allTickets.filter(t => !['resolved','closed'].includes((t.status||'').toLowerCase()));\nconst resolvedTickets = allTickets.filter(t => ['resolved','closed'].includes((t.status||'').toLowerCase()));\n\nconst breached = openTickets.filter(t => { const due = new Date(t.slaDueAt || 0); return due < now; });\n\nconst catCount = {};\nfor (const t of openTickets) { const c = t.category || 'Other'; catCount[c] = (catCount[c] || 0) + 1; }\n\nconst rated = allTickets.filter(t => t.rating && parseFloat(t.rating) > 0);\nconst avgRating = rated.length > 0 ? (rated.reduce((s, t) => s + parseFloat(t.rating), 0) / rated.length).toFixed(1) : 'N/A';\n\nconst highOpen = openTickets.filter(t => t.priority === 'High').length;\nconst medOpen = openTickets.filter(t => t.priority === 'Medium').length;\nconst lowOpen = openTickets.filter(t => t.priority === 'Low').length;\n\nconst recent5 = openTickets.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)).slice(0, 5);\n\nconst lines = [\n `\\uD83C\\uDFE0 *Property Management Dashboard*`,\n `\\uD83D\\uDCC5 ${now.toLocaleDateString('en-IN', { weekday: 'long', day: 'numeric', month: 'long' })}`,\n '',\n `\\u2501\\u2501 *Ticket Overview* \\u2501\\u2501`,\n `\\uD83D\\uDCC2 Total Open: *${openTickets.length}*`,\n `\\u2705 Resolved: *${resolvedTickets.length}*`,\n `\\uD83D\\uDEA8 SLA Breaches: *${breached.length}*`,\n `\\u2B50 Avg Rating: *${avgRating}/5*`,\n '',\n `\\u2501\\u2501 *By Priority* \\u2501\\u2501`,\n `\\uD83D\\uDD34 High: ${highOpen} | \\uD83D\\uDFE1 Medium: ${medOpen} | \\uD83D\\uDFE2 Low: ${lowOpen}`,\n '',\n `\\u2501\\u2501 *By Category* \\u2501\\u2501`,\n ...Object.entries(catCount).map(([c, n]) => ` ${c}: ${n}`)\n];\n\nif (breached.length > 0) {\n lines.push('');\n lines.push(`\\u2501\\u2501 \\uD83D\\uDEA8 *SLA Breaches* \\u2501\\u2501`);\n for (const t of breached.slice(0, 5)) {\n const pe = { High: '\\uD83D\\uDD34', Medium: '\\uD83D\\uDFE1', Low: '\\uD83D\\uDFE2' }[t.priority] || '\\u26AA';\n lines.push(`${pe} *${t.ticketId}* \\u2013 ${t.category}`);\n lines.push(` Tenant: ${t.tenantName} | Status: ${t.status}`);\n }\n}\n\nif (recent5.length > 0) {\n lines.push('');\n lines.push('\\u2501\\u2501 *Recent Open Tickets* \\u2501\\u2501');\n for (const t of recent5) {\n const pe = { High: '\\uD83D\\uDD34', Medium: '\\uD83D\\uDFE1', Low: '\\uD83D\\uDFE2' }[t.priority] || '\\u26AA';\n let created = t.createdAt;\n try { created = new Date(t.createdAt).toLocaleDateString('en-IN', { day:'numeric', month:'short' }); } catch(e) {}\n lines.push(`${pe} *${t.ticketId}* \\u2013 ${t.category} (${t.status})`);\n lines.push(` ${t.tenantName} | ${created}`);\n }\n}\n\nreturn [{ json: { phone: requesterPhone, dashboardMessage: lines.join('\\n') } }];"
},
"typeVersion": 2
},
{
"id": "5a791ebb-f34c-4391-b8fd-faedd270d24b",
"name": "WATI \u2013 Send Dashboard",
"type": "n8n-nodes-wati.wati",
"position": [
1792,
1696
],
"parameters": {
"target": "={{ $('Build Landlord Dashboard').item.json.phone }}",
"messageText": "={{ $('Build Landlord Dashboard').item.json.dashboardMessage }}"
},
"credentials": {
"watiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "9f7fc13e-880b-499a-8aee-340878112a99",
"name": "Schedule Trigger \u2013 Daily SLA Check 8AM",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
2464,
1088
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8 * * *"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "dfb02e9b-712e-4911-b7ec-611f09aae8f3",
"name": "Google Sheets \u2013 Read Tickets (SLA)",
"type": "n8n-nodes-base.googleSheets",
"position": [
2672,
1088
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1F7rsFG0c-ORJPf9t0hb2nwSMkXqxtyAp_5IyIV4rkdc/edit#gid=0",
"cachedResultName": "Tickets"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1F7rsFG0c-ORJPf9t0hb2nwSMkXqxtyAp_5IyIV4rkdc",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1F7rsFG0c-ORJPf9t0hb2nwSMkXqxtyAp_5IyIV4rkdc/edit?usp=drivesdk",
"cachedResultName": "Untitled spreadsheet"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "9af61a1c-e535-4209-ab00-f5369dbf7523",
"name": "Check SLA Breaches",
"type": "n8n-nodes-base.code",
"position": [
2912,
1088
],
"parameters": {
"jsCode": "// Daily SLA Breach Check\nconst allRows = $input.all();\nconst now = new Date();\n\nconst openTickets = allRows.map(r => r.json).filter(t => !['resolved','closed'].includes((t.status||'').toLowerCase()));\n\nif (openTickets.length === 0) {\n return [{ json: { message: 'No open tickets \\u2013 all clear!', hasBreaches: false } }];\n}\n\nconst breached = [];\nconst dueSoon = [];\nconst onTrack = [];\n\nfor (const t of openTickets) {\n const due = new Date(t.slaDueAt || 0);\n const hoursLeft = (due - now) / (1000 * 60 * 60);\n if (hoursLeft < 0) breached.push({ ...t, hoursOverdue: Math.abs(hoursLeft).toFixed(1) });\n else if (hoursLeft <= 4) dueSoon.push({ ...t, hoursLeft: hoursLeft.toFixed(1) });\n else onTrack.push(t);\n}\n\nconst pe = { High: '\\uD83D\\uDD34', Medium: '\\uD83D\\uDFE1', Low: '\\uD83D\\uDFE2' };\n\nconst landlordLines = [\n `\\uD83D\\uDCCA *Daily SLA Report \\u2013 ${now.toLocaleDateString('en-IN')}*`,\n '',\n `\\uD83D\\uDCC2 Open: ${openTickets.length} | \\uD83D\\uDEA8 Breached: ${breached.length} | \\u26A0\\uFE0F Due Soon: ${dueSoon.length} | \\u2705 On Track: ${onTrack.length}`,\n ''\n];\n\nif (breached.length > 0) {\n landlordLines.push('\\uD83D\\uDEA8 *SLA BREACHED \\u2013 Immediate Action Required:*');\n for (const t of breached) {\n landlordLines.push(`${pe[t.priority]||'\\u26AA'} *${t.ticketId}* \\u2013 ${t.category} (${t.hoursOverdue}h overdue)`);\n landlordLines.push(` \\uD83D\\uDC64 ${t.tenantName} | Team: ${t.assignedTeam} | Status: ${t.status}`);\n }\n landlordLines.push('');\n}\n\nif (dueSoon.length > 0) {\n landlordLines.push('\\u26A0\\uFE0F *Due Within 4 Hours:*');\n for (const t of dueSoon) {\n landlordLines.push(`${pe[t.priority]||'\\u26AA'} *${t.ticketId}* \\u2013 ${t.category} (${t.hoursLeft}h left)`);\n landlordLines.push(` \\uD83D\\uDC64 ${t.tenantName} | Team: ${t.assignedTeam}`);\n }\n}\n\nconst teamAlerts = breached.map(t => ({\n teamPhone: t.teamPhone,\n teamMessage: [\n `\\uD83D\\uDEA8 *SLA BREACH ALERT*`,\n `\\uD83C\\uDFAB Ticket *${t.ticketId}* is *${t.hoursOverdue} hours overdue!*`,\n `\\uD83C\\uDFF7\\uFE0F ${t.category} | ${pe[t.priority]||'\\u26AA'} ${t.priority}`,\n `\\uD83D\\uDC64 Tenant: ${t.tenantName} (${t.phone})`,\n `\\uD83D\\uDCDD Issue: ${t.issueText}`,\n '',\n 'Please resolve or update immediately!',\n `*update ${t.ticketId} in-progress <your note>*`\n ].join('\\n')\n}));\n\nreturn [{ json: {\n landlordPhone: 'LANDLORD_WHATSAPP_NUMBER',\n landlordReport: landlordLines.join('\\n'),\n teamAlerts,\n hasBreaches: breached.length > 0\n}}];"
},
"typeVersion": 2
},
{
"id": "63d80507-51ba-456e-bc72-e8458181785a",
"name": "WATI \u2013 Send SLA Report to Landlord",
"type": "n8n-nodes-wati.wati",
"position": [
3248,
944
],
"parameters": {
"target": "=LANDLORD_WHATSAPP_NUMBER",
"messageText": "={{ $('Check SLA Breaches').item.json.landlordReport }}"
},
"credentials": {
"watiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "027ffd3b-2912-4f76-aaf1-187df092dcf3",
"name": "Split Team Alerts",
"type": "n8n-nodes-base.code",
"position": [
3152,
1152
],
"parameters": {
"jsCode": "// Send breach alerts to each maintenance team\nconst alerts = $('Check SLA Breaches').item.json.teamAlerts || [];\nif (alerts.length === 0) return [{ json: { message: 'No team alerts to send' } }];\nreturn alerts.map(a => ({ json: a }));"
},
"typeVersion": 2
},
{
"id": "3cd2bc71-e6e8-4923-9f2e-f8d6f985c477",
"name": "WATI \u2013 Alert Teams of SLA Breach",
"type": "n8n-nodes-wati.wati",
"position": [
3392,
1152
],
"parameters": {
"target": "={{ $json.teamPhone }}",
"messageText": "={{ $json.teamMessage }}"
},
"credentials": {
"watiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "7b78a766-e12b-46dc-a871-02d7bbe65c33",
"name": "Wati Trigger",
"type": "n8n-nodes-wati.watiTrigger",
"position": [
432,
928
],
"parameters": {
"event": "messageReceived"
},
"credentials": {
"watiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1,
"alwaysOutputData": true
}
],
"connections": {
"Parse Rating": {
"main": [
[
{
"node": "Google Sheets \u2013 Save Rating",
"type": "main",
"index": 0
}
]
]
},
"Wati Trigger": {
"main": [
[
{
"node": "Master Router",
"type": "main",
"index": 0
}
]
]
},
"Master Router": {
"main": [
[
{
"node": "Google Sheets \u2013 Read Tickets (Update)",
"type": "main",
"index": 0
}
],
[
{
"node": "Parse Rating",
"type": "main",
"index": 0
}
],
[
{
"node": "Google Sheets \u2013 Read My Tickets",
"type": "main",
"index": 0
}
],
[
{
"node": "Google Sheets \u2013 Read All Tickets (Dashboard)",
"type": "main",
"index": 0
}
],
[
{
"node": "Classify Issue & Create Ticket",
"type": "main",
"index": 0
}
]
]
},
"Split Team Alerts": {
"main": [
[
{
"node": "WATI \u2013 Alert Teams of SLA Breach",
"type": "main",
"index": 0
}
]
]
},
"Check SLA Breaches": {
"main": [
[
{
"node": "WATI \u2013 Send SLA Report to Landlord",
"type": "main",
"index": 0
},
{
"node": "Split Team Alerts",
"type": "main",
"index": 0
}
]
]
},
"Parse Status Update": {
"main": [
[
{
"node": "Google Sheets \u2013 Update Ticket Status",
"type": "main",
"index": 0
}
]
]
},
"Build My Tickets View": {
"main": [
[
{
"node": "WATI \u2013 Send My Tickets",
"type": "main",
"index": 0
}
]
]
},
"Build Landlord Dashboard": {
"main": [
[
{
"node": "WATI \u2013 Send Dashboard",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets \u2013 Save Rating": {
"main": [
[
{
"node": "WATI \u2013 Send Rating Thanks",
"type": "main",
"index": 0
}
]
]
},
"Classify Issue & Create Ticket": {
"main": [
[
{
"node": "Google Sheets \u2013 Create Ticket",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets \u2013 Create Ticket": {
"main": [
[
{
"node": "WATI \u2013 Confirm to Tenant",
"type": "main",
"index": 0
},
{
"node": "WATI \u2013 Notify Maintenance Team",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets \u2013 Read My Tickets": {
"main": [
[
{
"node": "Build My Tickets View",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets \u2013 Read Tickets (SLA)": {
"main": [
[
{
"node": "Check SLA Breaches",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets \u2013 Update Ticket Status": {
"main": [
[
{
"node": "WATI \u2013 Acknowledge Team",
"type": "main",
"index": 0
},
{
"node": "WATI \u2013 Notify Tenant of Update",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets \u2013 Read Tickets (Update)": {
"main": [
[
{
"node": "Parse Status Update",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger \u2013 Daily SLA Check 8AM": {
"main": [
[
{
"node": "Google Sheets \u2013 Read Tickets (SLA)",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets \u2013 Read All Tickets (Dashboard)": {
"main": [
[
{
"node": "Build Landlord Dashboard",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleSheetsOAuth2ApiwatiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Empower your property management with a high-efficiency automated support desk. This workflow manages the complete maintenance lifecycle—from initial issue classification and ticket creation to team assignment, status updates, and tenant feedback—all centralized through WhatsApp…
Source: https://n8n.io/workflows/13705/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Customer support managers and team leads Customer success teams monitoring satisfaction Product managers analyzing user feedback Business analysts measuring support metrics Operations managers optimiz
Ensure health and safety with a fully automated medication adherence system. This workflow manages the entire patient care cycle—from scheduled dosage reminders to interactive logging and automated ca
Streamline your automotive service center's operations with this comprehensive automation. This workflow manages the entire customer lifecycle—from automated service reminders and instant appointment
Elevate your shopping experience with an AI-driven personal assistant that lives right in your WhatsApp. This template automates the entire lifecycle of a shopping list—from intelligent intake and liv
Streamline your clinic's operations with a fully automated patient communication system. This workflow manages the entire appointment lifecycle—from automated morning reminders to real-time confirmati