AutomationFlowsAI & RAG › Lu2 Activity 2b — Client Rapport Assistant with Human Handover

Lu2 Activity 2b — Client Rapport Assistant with Human Handover

LU2 Activity 2b — Client Rapport Assistant with Human Handover. Uses agent, lmChatGoogleGemini, outputParserStructured, googleSheets. Webhook trigger; 13 nodes.

Webhook trigger★★★★☆ complexityAI-powered13 nodesAgentGoogle Gemini ChatOutput Parser StructuredGoogle SheetsGmail
AI & RAG Trigger: Webhook Nodes: 13 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow follows the Agent → Gmail recipe pattern — see all workflows that pair these two integrations.

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 →

Download .json
{
  "name": "LU2 Activity 2b \u2014 Client Rapport Assistant with Human Handover",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "meridian-client-query",
        "responseMode": "responseNode",
        "options": {
          "allowedOrigins": "*"
        }
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        -1100,
        0
      ],
      "id": "c2000000-0001-4000-8000-000000000001",
      "name": "Client Query Webhook"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "d2000000-0001-4000-8000-000000000011",
              "name": "ticketId",
              "value": "=MAM-{{ $now.format('yyyyLLdd') }}-{{ $execution.id }}",
              "type": "string"
            },
            {
              "id": "d2000000-0001-4000-8000-000000000012",
              "name": "receivedAt",
              "value": "={{ $now.toISO() }}",
              "type": "string"
            },
            {
              "id": "d2000000-0001-4000-8000-000000000013",
              "name": "clientName",
              "value": "={{ $json.body.clientName.trim() }}",
              "type": "string"
            },
            {
              "id": "d2000000-0001-4000-8000-000000000014",
              "name": "clientEmail",
              "value": "={{ $json.body.clientEmail.trim().toLowerCase() }}",
              "type": "string"
            },
            {
              "id": "d2000000-0001-4000-8000-000000000015",
              "name": "accountRef",
              "value": "={{ $json.body.accountRef.trim().toUpperCase() }}",
              "type": "string"
            },
            {
              "id": "d2000000-0001-4000-8000-000000000016",
              "name": "portfolio",
              "value": "={{ $json.body.portfolio }}",
              "type": "string"
            },
            {
              "id": "d2000000-0001-4000-8000-000000000017",
              "name": "channel",
              "value": "={{ $json.body.channel || 'Website chat' }}",
              "type": "string"
            },
            {
              "id": "d2000000-0001-4000-8000-000000000018",
              "name": "message",
              "value": "={{ $json.body.message.trim() }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -880,
        0
      ],
      "id": "c2000000-0002-4000-8000-000000000002",
      "name": "Normalise Query"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=A client of Meridian Asset Management has raised a concern.\n\nTicket: {{ $json.ticketId }}\nClient: {{ $json.clientName }}\nAccount reference: {{ $json.accountRef }}\nPortfolio: {{ $json.portfolio }}\nChannel: {{ $json.channel }}\nReceived: {{ $json.receivedAt }}\n\nTheir message, verbatim:\n\"\"\"\n{{ $json.message }}\n\"\"\"\n\nClassify it, flag it, and draft the relationship manager's reply.",
        "hasOutputParser": true,
        "options": {
          "systemMessage": "You are the Client Rapport Assistant for Meridian Asset Management, a licensed fund manager in Singapore. Client relationship managers use you to draft replies to concerned investment clients.\n\nYou do not speak to clients. Everything you write is a DRAFT that a licensed relationship manager reads and approves before it is sent. Write as if a regulator will read it, because one might.\n\n## What you must do for every query\n1. Classify the concern.\n2. Read the client's emotional tone honestly \u2014 do not soften it. A furious client is \"Angry\", not \"Concerned\".\n3. Raise a compliance flag for anything that needs a human's attention.\n4. Draft a reply that is warm, specific to what they actually said, and strictly non-advisory.\n\n## THE NON-ADVISORY RULE \u2014 this is the rule that matters\nYou are NOT licensed to give financial advice, and neither is this workflow. In the draft you must NEVER:\n- recommend buying, selling, holding, switching or redeeming anything;\n- predict, forecast or estimate future returns, prices or NAV;\n- guarantee, promise or imply any outcome (\"markets always recover\", \"it will bounce back\", \"you will not lose money\");\n- tell the client their portfolio is suitable, unsuitable, safe, risky, or right for them;\n- comment on whether now is a good or bad time to invest, redeem or wait;\n- name a specific product, fund or asset as a course of action;\n- state a fee, NAV, return figure or holding that was not given to you in the query.\n\nYou MAY: acknowledge the emotion by name, restate their concern accurately, explain the process and what happens next, describe factual and publicly known context in neutral terms, point to their statement or factsheet, and offer a call with their licensed relationship manager.\n\nWhen in doubt, say less and offer the call.\n\n## Compliance flags \u2014 raise every one that applies\n- ADVICE_REQUESTED \u2014 the client asks what they should do, or asks you to decide for them.\n- GUARANTEE_SOUGHT \u2014 the client asks you to promise a return, a recovery, or that they will not lose money.\n- COMPLAINT \u2014 the client expresses dissatisfaction with Meridian, its staff, its fees or its conduct.\n- WITHDRAWAL_INTENT \u2014 the client raises redeeming, withdrawing, closing or moving their account.\n- VULNERABLE_CLIENT \u2014 the client mentions distress, illness, bereavement, retirement savings they cannot afford to lose, or an inability to cope.\n- LEGAL_OR_MEDIA_THREAT \u2014 the client mentions a lawyer, a regulator, MAS, the press or social media.\n\nSet `escalate` to true if you raise ANY of: ADVICE_REQUESTED, GUARANTEE_SOUGHT, VULNERABLE_CLIENT, LEGAL_OR_MEDIA_THREAT. Those four cannot be answered by a drafted email alone.\n\n## The draft\n`draftReply` is the body of the letter only: 2 to 4 short paragraphs, each wrapped in <p style=\"margin:0 0 16px;\">.\nDo NOT write a greeting, a sign-off, a disclaimer or a reference number \u2014 the letterhead, \"Dear <name>\", the sign-off and the regulatory disclaimer are added automatically by the system and must not be duplicated.\n\nStructure the body: acknowledge what they said and how they feel (first sentence, no throat-clearing) -> give factual, non-advisory context -> say exactly what happens next and offer the call. Under 180 words. Plain English. No exclamation marks, no jargon, no \"rest assured\", no \"unprecedented times\".\n\nIf you raised ADVICE_REQUESTED or GUARANTEE_SOUGHT, the draft must politely explain that the relationship manager cannot give a recommendation or a guarantee by email, and must offer a call instead.\n\n## Output\nReturn ONLY a JSON object with keys: ticketId, concernCategory, emotionalTone, urgency, complianceFlags, escalate, suggestedSubject, draftReply.\n- concernCategory: one of \"Portfolio Performance\", \"Market Volatility\", \"NAV Fluctuation\", \"Fees & Charges\", \"Withdrawal / Redemption\", \"Statement or Reporting\", \"Other\".\n- emotionalTone: one of \"Calm\", \"Concerned\", \"Anxious\", \"Frustrated\", \"Angry\", \"Distressed\".\n- urgency: one of \"Low\", \"Medium\", \"High\".\n- complianceFlags: an array of the flag strings above (empty array when none)."
        }
      },
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 3.1,
      "position": [
        -660,
        0
      ],
      "id": "c2000000-0003-4000-8000-000000000003",
      "name": "Rapport AI Agent"
    },
    {
      "parameters": {
        "modelName": "models/gemini-2.5-flash",
        "options": {
          "temperature": 0.2
        }
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "typeVersion": 1.1,
      "position": [
        -700,
        220
      ],
      "id": "c2000000-0004-4000-8000-000000000004",
      "name": "Google Gemini Chat Model",
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsonSchemaExample": "{\n  \"ticketId\": \"MAM-20260710-1042\",\n  \"concernCategory\": \"Market Volatility\",\n  \"emotionalTone\": \"Anxious\",\n  \"urgency\": \"High\",\n  \"complianceFlags\": [\n    \"ADVICE_REQUESTED\",\n    \"WITHDRAWAL_INTENT\"\n  ],\n  \"escalate\": true,\n  \"suggestedSubject\": \"Your enquiry about recent market movements (MAM-20260710-1042)\",\n  \"draftReply\": \"<p style=\\\"margin:0 0 16px;\\\">Thank you for writing, and I am sorry that the recent movement in your portfolio has been worrying.</p><p style=\\\"margin:0 0 16px;\\\">I am not able to tell you what to do with your holdings by email, and I would not want to give you a recommendation without first understanding your circumstances properly.</p><p style=\\\"margin:0 0 16px;\\\">Your relationship manager will call you within one business day to talk this through.</p>\"\n}"
      },
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [
        -500,
        220
      ],
      "id": "c2000000-0005-4000-8000-000000000005",
      "name": "Draft Parser"
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { \"status\": \"received\", \"ticketId\": $('Normalise Query').first().json.ticketId, \"urgency\": $('Rapport AI Agent').first().json.output.urgency, \"escalated\": $('Rapport AI Agent').first().json.output.escalate, \"message\": \"Thank you. Your message has reached the Meridian client relationship team and has been logged under the reference below. A licensed relationship manager will review it personally and reply to you by email. We do not send investment advice through this chat.\" } }}",
        "options": {}
      },
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        -440,
        0
      ],
      "id": "c2000000-0006-4000-8000-000000000006",
      "name": "Acknowledge Client"
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "REPLACE_WITH_YOUR_SHEET_ID",
          "mode": "list",
          "cachedResultName": "Meridian Client Rapport"
        },
        "sheetName": {
          "__rl": true,
          "value": "Drafts",
          "mode": "list",
          "cachedResultName": "Drafts"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Timestamp": "={{ $now.toISO() }}",
            "Ticket ID": "={{ $('Normalise Query').first().json.ticketId }}",
            "Client Name": "={{ $('Normalise Query').first().json.clientName }}",
            "Client Email": "={{ $('Normalise Query').first().json.clientEmail }}",
            "Account Ref": "={{ $('Normalise Query').first().json.accountRef }}",
            "Portfolio": "={{ $('Normalise Query').first().json.portfolio }}",
            "Client Message": "={{ $('Normalise Query').first().json.message }}",
            "Concern Category": "={{ $('Rapport AI Agent').first().json.output.concernCategory }}",
            "Emotional Tone": "={{ $('Rapport AI Agent').first().json.output.emotionalTone }}",
            "Urgency": "={{ $('Rapport AI Agent').first().json.output.urgency }}",
            "Compliance Flags": "={{ ($('Rapport AI Agent').first().json.output.complianceFlags || []).join(', ') }}",
            "Escalate": "={{ $('Rapport AI Agent').first().json.output.escalate }}",
            "Draft Reply": "={{ $('Rapport AI Agent').first().json.output.draftReply.replace(/<[^>]+>/g, ' ').trim() }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "Timestamp",
              "displayName": "Timestamp",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Ticket ID",
              "displayName": "Ticket ID",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Client Name",
              "displayName": "Client Name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Client Email",
              "displayName": "Client Email",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Account Ref",
              "displayName": "Account Ref",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Portfolio",
              "displayName": "Portfolio",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Client Message",
              "displayName": "Client Message",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Concern Category",
              "displayName": "Concern Category",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Emotional Tone",
              "displayName": "Emotional Tone",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Urgency",
              "displayName": "Urgency",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Compliance Flags",
              "displayName": "Compliance Flags",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Escalate",
              "displayName": "Escalate",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Draft Reply",
              "displayName": "Draft Reply",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        -220,
        0
      ],
      "id": "c2000000-0007-4000-8000-000000000007",
      "name": "Log Draft",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "sendAndWait",
        "sendTo": "REPLACE_WITH_YOUR_EMAIL@example.com",
        "subject": "=[{{ $('Rapport AI Agent').first().json.output.escalate ? 'ESCALATE' : 'REVIEW' }}] Draft reply to {{ $('Normalise Query').first().json.clientName }} \u2014 {{ $('Normalise Query').first().json.ticketId }}",
        "message": "=APPROVAL REQUIRED \u2014 draft reply to a client\n\nTicket:      {{ $('Normalise Query').first().json.ticketId }}\nClient:      {{ $('Normalise Query').first().json.clientName }} <{{ $('Normalise Query').first().json.clientEmail }}>\nAccount:     {{ $('Normalise Query').first().json.accountRef }} \u2014 {{ $('Normalise Query').first().json.portfolio }}\n\nAGENT ASSESSMENT\nCategory:    {{ $('Rapport AI Agent').first().json.output.concernCategory }}\nTone:        {{ $('Rapport AI Agent').first().json.output.emotionalTone }}\nUrgency:     {{ $('Rapport AI Agent').first().json.output.urgency }}\nFlags:       {{ ($('Rapport AI Agent').first().json.output.complianceFlags || []).length ? $('Rapport AI Agent').first().json.output.complianceFlags.join(', ') : 'none' }}\nEscalate:    {{ $('Rapport AI Agent').first().json.output.escalate ? 'YES \u2014 a drafted email alone is not sufficient. Call the client.' : 'no' }}\n\nCLIENT SAID\n{{ $('Normalise Query').first().json.message }}\n\nPROPOSED SUBJECT\n{{ $('Rapport AI Agent').first().json.output.suggestedSubject }}\n\nPROPOSED REPLY\n{{ $('Rapport AI Agent').first().json.output.draftReply.replace(/<[^>]+>/g, '') }}\n\n----\nApprove to send this reply to the client as-is. Decline to send it back for a manual rewrite.\nYou are the licensed representative. Nothing reaches the client unless you approve it.",
        "approvalOptions": {
          "values": {
            "approvalType": "double"
          }
        },
        "options": {}
      },
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        0,
        0
      ],
      "id": "c2000000-0008-4000-8000-000000000008",
      "name": "RM Approval",
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "e2000000-0001-4000-8000-000000000021",
              "leftValue": "={{ $json.data.approved }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        220,
        0
      ],
      "id": "c2000000-0009-4000-8000-000000000009",
      "name": "If Approved"
    },
    {
      "parameters": {
        "sendTo": "={{ $('Normalise Query').first().json.clientEmail }}",
        "subject": "={{ $('Rapport AI Agent').first().json.output.suggestedSubject }}",
        "emailType": "html",
        "message": "=<!DOCTYPE html>\n<html>\n<body style=\"margin:0;padding:0;background:#f5f7fa;\">\n  <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#f5f7fa;padding:32px 12px;\">\n    <tr><td align=\"center\">\n      <table role=\"presentation\" width=\"620\" cellpadding=\"0\" cellspacing=\"0\" style=\"max-width:620px;background:#ffffff;border:1px solid #e3e8ee;border-radius:10px;overflow:hidden;font-family:Arial,Helvetica,sans-serif;\">\n\n        <tr><td style=\"background:#10243e;padding:24px 32px;\">\n          <span style=\"display:inline-block;width:36px;height:36px;line-height:36px;text-align:center;background:#c9a227;color:#10243e;font-weight:bold;font-size:15px;border-radius:4px;\">M</span>\n          <span style=\"color:#ffffff;font-size:17px;font-weight:bold;margin-left:12px;vertical-align:middle;\">Meridian Asset Management</span>\n          <div style=\"color:#8fa6c0;font-size:11px;letter-spacing:1.5px;text-transform:uppercase;margin-top:6px;\">Client Relationship Team &middot; Singapore</div>\n        </td></tr>\n\n        <tr><td style=\"padding:32px;color:#1b2733;font-size:15px;line-height:1.65;\">\n          <p style=\"margin:0 0 18px;\">Dear {{ $('Normalise Query').first().json.clientName }},</p>\n\n          {{ $('Rapport AI Agent').first().json.output.draftReply }}\n\n          <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\" style=\"margin:26px 0;border-top:1px solid #e3e8ee;border-bottom:1px solid #e3e8ee;\">\n            <tr>\n              <td style=\"padding:14px 0;font-size:13px;color:#5b6b7b;width:45%;\">Reference</td>\n              <td style=\"padding:14px 0;font-size:13px;color:#1b2733;font-weight:bold;\">{{ $('Normalise Query').first().json.ticketId }}</td>\n            </tr>\n            <tr>\n              <td style=\"padding:0 0 14px;font-size:13px;color:#5b6b7b;\">Account</td>\n              <td style=\"padding:0 0 14px;font-size:13px;color:#1b2733;font-weight:bold;\">&bull;&bull;&bull;&bull;{{ $('Normalise Query').first().json.accountRef.slice(-4) }}</td>\n            </tr>\n            <tr>\n              <td style=\"padding:0 0 14px;font-size:13px;color:#5b6b7b;\">Portfolio</td>\n              <td style=\"padding:0 0 14px;font-size:13px;color:#1b2733;font-weight:bold;\">{{ $('Normalise Query').first().json.portfolio }}</td>\n            </tr>\n          </table>\n\n          <p style=\"margin:0 0 4px;\">Yours sincerely,</p>\n          <p style=\"margin:0;font-weight:bold;\">Client Relationship Team</p>\n          <p style=\"margin:2px 0 0;color:#5b6b7b;font-size:13px;\">Meridian Asset Management, Singapore</p>\n          <p style=\"margin:14px 0 0;color:#5b6b7b;font-size:13px;\">You can reply directly to this email, or call us on +65 6800 5678.</p>\n        </td></tr>\n\n        <tr><td style=\"background:#f5f7fa;border-top:1px solid #e3e8ee;padding:22px 32px;color:#5b6b7b;font-size:11.5px;line-height:1.6;\">\n          <p style=\"margin:0 0 8px;\"><strong>Important.</strong> This email is provided for information only. It does not constitute financial advice, an offer, or a recommendation to buy, sell or hold any investment product, and it does not take account of your objectives, financial situation or particular needs. Past performance is not indicative of future performance. The value of investments and the income from them may fall as well as rise, and you may not get back the amount you invested.</p>\n          <p style=\"margin:0 0 8px;\">This message was drafted with assistance from an AI system and reviewed and approved by a licensed representative of Meridian Asset Management before it was sent.</p>\n          <p style=\"margin:0;color:#8b98a5;\">Meridian Asset Management is a fictitious institution created for the Tertiary Infotech course &ldquo;No Code and Low Code Agentic AI Applications&rdquo;. No investment service is offered and no advice of any kind is given.</p>\n        </td></tr>\n\n      </table>\n    </td></tr>\n  </table>\n</body>\n</html>",
        "options": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        460,
        -140
      ],
      "id": "c2000000-0010-4000-8000-000000000010",
      "name": "Send Approved Reply",
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "REPLACE_WITH_YOUR_SHEET_ID",
          "mode": "list",
          "cachedResultName": "Meridian Client Rapport"
        },
        "sheetName": {
          "__rl": true,
          "value": "Approved_Replies",
          "mode": "list",
          "cachedResultName": "Approved_Replies"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Timestamp": "={{ $now.toISO() }}",
            "Ticket ID": "={{ $('Normalise Query').first().json.ticketId }}",
            "Client Name": "={{ $('Normalise Query').first().json.clientName }}",
            "Client Email": "={{ $('Normalise Query').first().json.clientEmail }}",
            "Concern Category": "={{ $('Rapport AI Agent').first().json.output.concernCategory }}",
            "Emotional Tone": "={{ $('Rapport AI Agent').first().json.output.emotionalTone }}",
            "Compliance Flags": "={{ ($('Rapport AI Agent').first().json.output.complianceFlags || []).join(', ') }}",
            "Approved By": "=REPLACE_WITH_YOUR_EMAIL@example.com",
            "Approved At": "={{ $now.toISO() }}",
            "Subject Sent": "={{ $('Rapport AI Agent').first().json.output.suggestedSubject }}",
            "Reply Sent": "={{ $('Rapport AI Agent').first().json.output.draftReply.replace(/<[^>]+>/g, ' ').trim() }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "Timestamp",
              "displayName": "Timestamp",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Ticket ID",
              "displayName": "Ticket ID",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Client Name",
              "displayName": "Client Name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Client Email",
              "displayName": "Client Email",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Concern Category",
              "displayName": "Concern Category",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Emotional Tone",
              "displayName": "Emotional Tone",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Compliance Flags",
              "displayName": "Compliance Flags",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Approved By",
              "displayName": "Approved By",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Approved At",
              "displayName": "Approved At",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Subject Sent",
              "displayName": "Subject Sent",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Reply Sent",
              "displayName": "Reply Sent",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        700,
        -140
      ],
      "id": "c2000000-0011-4000-8000-000000000011",
      "name": "Log Approved Reply",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "REPLACE_WITH_YOUR_SHEET_ID",
          "mode": "list",
          "cachedResultName": "Meridian Client Rapport"
        },
        "sheetName": {
          "__rl": true,
          "value": "Handover_Queue",
          "mode": "list",
          "cachedResultName": "Handover_Queue"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Timestamp": "={{ $now.toISO() }}",
            "Ticket ID": "={{ $('Normalise Query').first().json.ticketId }}",
            "Client Name": "={{ $('Normalise Query').first().json.clientName }}",
            "Client Email": "={{ $('Normalise Query').first().json.clientEmail }}",
            "Client Message": "={{ $('Normalise Query').first().json.message }}",
            "Concern Category": "={{ $('Rapport AI Agent').first().json.output.concernCategory }}",
            "Emotional Tone": "={{ $('Rapport AI Agent').first().json.output.emotionalTone }}",
            "Compliance Flags": "={{ ($('Rapport AI Agent').first().json.output.complianceFlags || []).join(', ') }}",
            "Rejected Draft": "={{ $('Rapport AI Agent').first().json.output.draftReply.replace(/<[^>]+>/g, ' ').trim() }}",
            "Assigned To": "=REPLACE_WITH_HUMAN_AGENT_EMAIL@example.com",
            "Status": "=Assigned to human agent \u2014 awaiting personal contact with the client"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "Timestamp",
              "displayName": "Timestamp",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Ticket ID",
              "displayName": "Ticket ID",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Client Name",
              "displayName": "Client Name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Client Email",
              "displayName": "Client Email",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Client Message",
              "displayName": "Client Message",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Concern Category",
              "displayName": "Concern Category",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Emotional Tone",
              "displayName": "Emotional Tone",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Compliance Flags",
              "displayName": "Compliance Flags",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Rejected Draft",
              "displayName": "Rejected Draft",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Assigned To",
              "displayName": "Assigned To",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "displayName": "Status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        460,
        160
      ],
      "id": "c2000000-0012-4000-8000-000000000012",
      "name": "Assign to Human Agent",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "sendTo": "REPLACE_WITH_HUMAN_AGENT_EMAIL@example.com",
        "subject": "=[ACTION REQUIRED] Contact {{ $('Normalise Query').first().json.clientName }} personally \u2014 {{ $('Normalise Query').first().json.ticketId }}",
        "emailType": "text",
        "message": "=The AI-drafted reply for {{ $('Normalise Query').first().json.ticketId }} was DECLINED by the relationship manager.\n\nThis ticket is now assigned to you. The client has NOT been contacted and is still waiting.\nDo not send the drafted text. Speak to the client yourself, then record the outcome.\n\nCLIENT\n  Name:      {{ $('Normalise Query').first().json.clientName }}\n  Email:     {{ $('Normalise Query').first().json.clientEmail }}\n  Account:   {{ $('Normalise Query').first().json.accountRef }}\n  Portfolio: {{ $('Normalise Query').first().json.portfolio }}\n\nASSESSMENT\n  Category:  {{ $('Rapport AI Agent').first().json.output.concernCategory }}\n  Tone:      {{ $('Rapport AI Agent').first().json.output.emotionalTone }}\n  Urgency:   {{ $('Rapport AI Agent').first().json.output.urgency }}\n  Flags:     {{ ($('Rapport AI Agent').first().json.output.complianceFlags || []).length ? $('Rapport AI Agent').first().json.output.complianceFlags.join(', ') : 'none' }}\n  Escalate:  {{ $('Rapport AI Agent').first().json.output.escalate ? 'YES \u2014 call the client, do not email.' : 'no' }}\n\nWHAT THE CLIENT SAID\n{{ $('Normalise Query').first().json.message }}\n\nTHE DRAFT THAT WAS DECLINED (for your context only \u2014 do not send it)\n{{ $('Rapport AI Agent').first().json.output.draftReply.replace(/<[^>]+>/g, '') }}\n\n----\nThe full record is in the Handover_Queue tab of the Meridian Client Rapport sheet.\nUpdate the Status column once you have spoken to the client.",
        "options": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        700,
        160
      ],
      "id": "c2000000-0013-4000-8000-000000000013",
      "name": "Email Human Agent",
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Client Query Webhook": {
      "main": [
        [
          {
            "node": "Normalise Query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalise Query": {
      "main": [
        [
          {
            "node": "Rapport AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Rapport AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Draft Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Rapport AI Agent",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Rapport AI Agent": {
      "main": [
        [
          {
            "node": "Acknowledge Client",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Acknowledge Client": {
      "main": [
        [
          {
            "node": "Log Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Draft": {
      "main": [
        [
          {
            "node": "RM Approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "RM Approval": {
      "main": [
        [
          {
            "node": "If Approved",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Approved": {
      "main": [
        [
          {
            "node": "Send Approved Reply",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Assign to Human Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Approved Reply": {
      "main": [
        [
          {
            "node": "Log Approved Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Assign to Human Agent": {
      "main": [
        [
          {
            "node": "Email Human Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "d9e3f5b2-6c7a-4d8e-b012-3c4d5e6f7a8b",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodeGroups": [],
  "id": "LU2Activity2bRapport",
  "tags": []
}

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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

LU2 Activity 2b — Client Rapport Assistant with Human Handover. Uses agent, lmChatGoogleGemini, outputParserStructured, googleSheets. Webhook trigger; 13 nodes.

Source: https://github.com/tertiarycourses/TGS-2026062147-No-Code-and-Low-Code-Agentic-AI-Applications/blob/main/labs/activity10-client-rapport/LU2-Activity2b-Client-Rapport-Assistant.json — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

leads. Uses supabase, gmail, formTrigger, httpRequest. Webhook trigger; 62 nodes.

Supabase, Gmail, Form Trigger +13
AI & RAG

This workflow receives a meeting voice recording via webhook, transcribes it with Deepgram, and uses Google Gemini to extract client details and next-step tasks. It then updates GoHighLevel, posts a t

High Level, Agent, Google Gemini Chat +5
AI & RAG

⏺ 🚀 How it works

Agent, Anthropic Chat, Output Parser Structured +6
AI & RAG

Enhance your support, onboarding, and internal knowledge workflows with an intelligent RAG-powered chatbot that responds using live data stored in Google Sheets. 🤖📚 Built for teams that rely on struct

Chat Trigger, Output Parser Structured, Memory Buffer Window +6
AI & RAG

LineOA. Uses httpRequest, agent, lmChatGoogleGemini, outputParserStructured. Webhook trigger; 69 nodes.

HTTP Request, Agent, Google Gemini Chat +3