8
n8n 中文网amn8n.com

房地产工作流

高级

这是一个Lead Nurturing, Multimodal AI领域的自动化工作流,包含 33 个节点。主要使用 If, Set, Code, Merge, Webhook 等节点。 AI房产匹配和自动日历调度的房地产聊天机器人

前置要求
  • HTTP Webhook 端点(n8n 会自动生成)
  • PostgreSQL 数据库连接信息
  • Google 账号和 Gmail API 凭证
  • OpenAI API Key
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "5AVHQCf6dHelWBYq",
  "meta": {
    "instanceId": "e0a17b2ba0e05724bed958a326c256befe19ed7fc877d0fdfe41ad323c43f73d"
  },
  "name": "属性工作流",
  "tags": [],
  "nodes": [
    {
      "id": "d1147020-69b9-4f81-be58-8e37e78e5832",
      "name": "LLM对话",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -240,
        860
      ],
      "parameters": {
        "text": "={{ $json.originalMessage }}",
        "options": {
          "systemMessage": "=You are a real estate assistant named PropPanda. You MUST follow this EXACT step-by-step workflow with intelligent information extraction:\n\n## CONTENT FILTER (FIRST PRIORITY)\n\nIMMEDIATE CHECK: Before any other processing, check if the user's message is related to real estate services:\n\nALLOWED TOPICS:\n- Property rental/purchase inquiries (HDB, condo, apartment)\n- Location preferences and property searches\n- Budget discussions for properties\n- Viewing appointments and property tours\n- Nearby amenities (MRT, schools, restaurants)\n- Human agent requests for property matters\n\nFORBIDDEN TOPICS - END CONVERSATION GRACEFULLY:\n- Adult/sexual content, escort services\n- Non-property services (food delivery, transport, medical, technical support)\n- Spam, advertisements, investment schemes\n- Personal conversations unrelated to property\n- Any illegal or inappropriate requests\n\nIF FORBIDDEN TOPIC DETECTED:\nRespond: \"I'm sorry, but I can only assist with property-related inquiries such as finding HDB flats, condos, or apartments for rent or purchase. If you have any property questions, I'd be happy to help! Otherwise, have a great day.\"\nSTOP ALL PROCESSING AND WAIT FOR NEW MESSAGE\n\n---\n\n## INFORMATION EXTRACTION SYSTEM\n\nALWAYS FIRST: Before asking any questions, scan the user's message for ANY of the following information and save what you find:\n\nPersonal Information Keywords:\n- Name: \"I am [name]\", \"my name is [name]\", \"name is [name]\", or any clear name introduction\n- Phone: Phone numbers (8-12 digits, with or without country codes, +65, etc.)\n- Email: Email addresses (any text containing @ symbol with domain)\n\nProperty Requirements Keywords:\n- Type: \"HDB\", \"condo\", \"condominium\", \"apartment\", \"flat\", \"studio\"\n- Location: \"near [location]\", \"in [area]\", \"[district name]\", \"around [place]\"\n- Budget: Any number followed by \"budget\", \"price range\", \"afford\", \"$\", \"SGD\", currency mentions\n- Timeline: Months/years like \"June 2025\", \"next month\", \"ASAP\", \"immediately\", \"by [date]\"\n- Citizenship: \"citizen\", \"PR\", \"permanent resident\", \"foreigner\", \"non-citizen\", \"expat\"\n\nEXTRACTION EXAMPLES:\n- \"I am John looking for condo near Newton, budget 2500\" → Extract: name=John, type=condo, location=near Newton, budget=2500\n- \"My name is Sarah, phone 91234567, need HDB in Tampines by March 2025\" → Extract: name=Sarah, phone=91234567, type=HDB, location=Tampines, timeline=March 2025\n\n---\n\n## WORKFLOW STEPS\n\nSTEP 1: HUMAN AGENT HANDOFF (PRIORITY CHECK)\n\nFIRST, check if user message contains any of these keywords/phrases:\n- \"human agent\", \"talk to human\", \"speak to agent\", \"connect me to agent\", \"real person\", \"live agent\", \"customer service\", \"speak to someone\"\n\nIF DETECTED:\n- Check personal details completeness (name, phone, email)\n- IF MISSING personal details:\n  - Respond: \"I'd be happy to connect you with one of our human agents! To ensure they can contact you, I'll need a few details first.\"\n  - COLLECT ONLY missing information sequentially (skip already provided):\n    - If name missing: \"Could you please provide your Full Name?\"\n    - If phone missing: \"What's your Phone Number?\"\n    - If email missing: \"What's your Email Address?\"\n  - After collecting missing details, IMMEDIATELY ask for message:\n    - \"Thank you! Now, what specific message would you like me to pass on to our human agent?\"\n    - STOP and WAIT for user's message response - DO NOT PROCEED until user provides their message\n  \n- IF personal details already available:\n  - Immediately ask: \"What specific message would you like me to pass on to our human agent?\"\n  - STOP and WAIT for user's message response - DO NOT PROCEED until user provides their message\n  \n- ONLY AFTER receiving user's message:\n  - THEN use Human agent tool with this format:\n    - Subject: \"Customer Handoff Request - {name}\"\n    - Email Content: \n      \n      Customer Details:\n      - Name: {name}\n      - Phone: {phone}\n      - Email: {email}\n      \n      Customer Message: {user_message}\n      \n      Property Requirements (if any collected):\n      {include any property requirements already gathered}\n      \n  - AFTER sending email, confirm: \"Thank you! I've forwarded your message to our human agent. They will contact you within 2 days at {email} or {phone}.\"\n  - STOP workflow and wait for user response\n\nIF NOT DETECTED, continue to Step 2\n\nSTEP 2: COLLECT PERSONAL INFORMATION\n\nAfter extracting available personal info:\n- Mental checklist: [name: ✓/✗] [phone: ✓/✗] [email: ✓/✗]\n- ONLY ask for missing information in this exact order:\n  - If name missing: \"Could you please provide your Full Name?\"\n  - If phone missing: \"What's your Phone Number?\"\n  - If email missing: \"What's your Email Address?\"\n- SKIP questions for already provided information\n- VALIDATE inputs:\n  - Phone: Must be 8-12 digits\n  - Email: Must contain @ and domain\n- NO CONFIRMATION STEP - proceed directly after collecting all personal details\n\nSTEP 3: COLLECT PROPERTY REQUIREMENTS\n\nAfter extracting available property requirements:\n- Mental checklist: [type: ✓/✗] [locations: ✓/✗] [budget: ✓/✗] [timeline: ✓/✗] [citizenship: ✓/✗]\n- ONLY ask for missing information in this exact order:\n  - If type missing: \"What Property Type are you looking for? (HDB / Condo / Apartment)\"\n  - If locations missing: \"What are your Preferred Locations or Districts?\"\n  - If budget missing: \"What's your Budget range?\"\n  - If timeline missing: \"When is your preferred Move-in Timeline?\"\n  - If citizenship missing: \"What's your Citizenship Status? (Singapore Citizen / PR / Foreigner)\"\n- SKIP questions for already provided information\n- NO CONFIRMATION STEP - proceed directly after collecting all requirements\n\nSTEP 4: FINAL JSON OUTPUT\nAfter collecting all information:\njson\n{\n\"personal_details\": {\n\"name\": \"{name}\",\n\"phone\": \"{phone}\",\n\"email\": \"{email}\"\n},\n\"properties\": {\n\"type\": \"{type}\",\n\"locations\": \"{locations}\",\n\"budget\": \"{budget}\",\n\"timeline\": \"{timeline}\",\n\"citizenship\": \"{citizenship}\"\n}\n}\n\n\nSTEP 5: PROPERTY SEARCH & DISPLAY\n- Use property search system to find matching properties based on collected requirements\n- Display properties with details such as:\n  - Property name/address\n  - Price/rent\n  - Property features (bedrooms, bathrooms, size, etc.)\n  - Property images if available\n  - Contact information or listing details\n\n\nSTEP 6: APPOINTMENT BOOKING\nAsk: \"Would you like to schedule a viewing for any of these properties?\"\n\nIf yes:\n- Ask: \"Could you please suggest a preferred date for the appointment?\"\n- Wait for user's date suggestion\n- Use get_Calendar tool to check availability for that date\n- IF NO SLOTS AVAILABLE: \"I'm sorry, but that date isn't available. Could you please suggest another date for the appointment?\"\n- IF AVAILABLE: Confirm the slot and send Gmail confirmation\n\n---\n\n## CRITICAL RULES\n\n1. CONTENT FILTER FIRST: Always check for non-property inquiries before any other processing\n2. EXTRACT FIRST, ASK LATER: Always scan user messages for information before asking questions\n3. NO REDUNDANT QUESTIONS: Never ask for information the user already provided\n4. NO CONFIRMATIONS: Skip confirmation steps to avoid repetition\n5. SEQUENTIAL COLLECTION: Ask for missing information in exact specified order\n6. HANDLE BULK RESPONSES: If user provides multiple pieces of info in one response, extract all of it\n7. VALIDATION: Ensure phone numbers and emails are properly formatted\n8. USER-SUGGESTED DATES: Always ask user to suggest appointment dates instead of checking next 3 days\n9. HUMAN AGENT MESSAGE MANDATORY: For human agent requests, NEVER send email without first collecting the user's specific message. Always ask \"What specific message would you like me to pass on to our human agent?\" and WAIT for response before proceeding.\n\n## CONVERSATION MEMORY\n- Track what information has been provided to avoid repetition\n- Remember user's previous responses in the same conversation\n- Don't re-ask questions that were already answered\n\n## AVAILABLE TOOLS\n- Property Search System: For finding and displaying properties based on user requirements  \n- Gmail: For appointment confirmations\n- get_Calendar: For checking available slots on user-suggested dates\n- Human agent: For customer handoff requests\n\nUser input: {{ $json.originalMessage }}"
        },
        "promptType": "define"
      },
      "typeVersion": 1.9
    },
    {
      "id": "301553ac-7c05-4ca9-a4d9-03ac5209ab69",
      "name": "PostgreSQL记忆",
      "type": "@n8n/n8n-nodes-langchain.memoryPostgresChat",
      "position": [
        -240,
        1120
      ],
      "parameters": {
        "tableName": "user_details",
        "sessionKey": "=harshassession123098456",
        "sessionIdType": "customKey",
        "contextWindowLength": 5000
      },
      "typeVersion": 1.3
    },
    {
      "id": "ded5c99e-7b47-4b02-8a4b-66bad2da9a95",
      "name": "解析数据",
      "type": "n8n-nodes-base.code",
      "position": [
        340,
        780
      ],
      "parameters": {
        "jsCode": "// Initialize default data structure\nlet data = {\n  personal: {\n    name: null,\n    phone: null,\n    email: null\n  },\n  property: {\n    type: null,\n    locations: null,\n    budget: null,\n    timeline: null,\n    citizenship: null\n  }\n};\n\n// Get input from webhook\nconst input = $input.first();\nif (!input || !input.json) {\n  return [{\n    json: {\n      sessionId: 'unknown',\n      personal: data.personal,\n      property: data.property,\n      ready: false\n    }\n  }];\n}\n\nconst sessionId = input.json.sessionId || 'unknown';\nconst rawText = input.json.output || '';\n\n// Attempt to parse JSON from text\ntry {\n  const jsonMatch = rawText.match(/{[\\s\\S]*}/);\n  const jsonString = jsonMatch ? jsonMatch[0] : '{}';\n  const parsedData = JSON.parse(jsonString);\n\n  // Personal info\n  if (parsedData.personal_details) {\n    data.personal.name = parsedData.personal_details.name || null;\n    data.personal.phone = parsedData.personal_details.phone || null;\n    data.personal.email = parsedData.personal_details.email || null;\n  }\n\n  // Property info\n  if (parsedData.properties) {\n    data.property.type = parsedData.properties.type || null;\n    data.property.timeline = parsedData.properties.timeline || null;\n    data.property.citizenship = parsedData.properties.citizenship || null;\n\n    // Clean \"locations\" value to extract just the MRT name\n    if (parsedData.properties.locations) {\n      data.property.locations = parsedData.properties.locations\n        .replace(/(near|beside|around|next to|opposite|close to)\\s+/gi, '')\n        .trim();\n    }\n\n    // Robust budget parsing (handles SGD 2,500 / $2500 / 2500 SGD etc.)\n    if (parsedData.properties.budget) {\n      const rawBudget = parsedData.properties.budget;\n      const cleanedBudget = rawBudget.replace(/[^\\d]/g, '');\n      data.property.budget = cleanedBudget ? parseInt(cleanedBudget, 10) : null;\n    }\n  }\n} catch (e) {\n  console.error('Error parsing JSON data:', e.message);\n}\n\n// Check readiness\nconst ready = Boolean(data.personal.name && data.personal.phone && data.personal.email);\n\n// Final output\nreturn [{\n  json: {\n    sessionId,\n    personal: data.personal,\n    property: data.property,\n    ready\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "caf89764-2c6f-43c7-8610-8db27d500b5f",
      "name": "保存个人信息",
      "type": "n8n-nodes-base.postgres",
      "position": [
        740,
        920
      ],
      "parameters": {
        "query": "INSERT INTO user_info (name, phone_number, email) VALUES ($1, $2, $3)",
        "options": {
          "queryReplacement": "={{ $('Parse Data').item.json.personal.name }}{{ $('Parse Data').item.json.personal.phone }}{{ $('Parse Data').item.json.personal.email }}{{ $('Parse Data').item.json.sessionId }}"
        },
        "operation": "executeQuery"
      },
      "typeVersion": 2.6
    },
    {
      "id": "c6f368f7-d0f3-4d2c-93bd-b1c0fa982ffc",
      "name": "查询属性",
      "type": "n8n-nodes-base.postgres",
      "position": [
        600,
        1040
      ],
      "parameters": {
        "query": "SELECT * FROM \"new_properties\"\nWHERE \"Property Type\" ILIKE '%' || $1 || '%'\n  AND \"Nearest MRT\" ILIKE '%' || $2 || '%'\n  AND \"Rental Price (SGD/month)\" <= CAST(regexp_replace($3, '[^0-9]', '', 'g') AS bigint)\nORDER BY \"Rental Price (SGD/month)\" ASC\nLIMIT 5;\n\n",
        "options": {
          "queryReplacement": "={{ $json.property.type }}{{ $json.property.locations }}{{ $json.property.budget }}"
        },
        "operation": "executeQuery"
      },
      "credentials": {
        "postgres": {
          "id": "Y3Im9kfoKd4hu5NL",
          "name": "Postgres letchu"
        }
      },
      "typeVersion": 2.6
    },
    {
      "id": "8f1e928d-c0e1-42cd-bc7a-93f630e4256e",
      "name": "就绪检查",
      "type": "n8n-nodes-base.if",
      "position": [
        320,
        1060
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.ready }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "6841173f-de6c-476a-a343-dbc84bf528e9",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -360,
        1080
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4",
          "cachedResultName": "gpt-4"
        },
        "options": {
          "temperature": 0
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "2acc09f6-d9bb-43f5-8045-35394f6384c5",
      "name": "使用AI格式化结果",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1120,
        940
      ],
      "parameters": {
        "text": "={{ $json }}",
        "options": {
          "systemMessage": "=You are a professional real estate assistant. Your task is to format property search results into a natural, engaging response for users.\n\nGiven property data, create a personalized response that:\n1. Acknowledges their search criteria\n2. Presents the properties in an organized, easy-to-read format\n3. Highlights key features like location, price, and property type\n4. Includes a friendly closing with next steps\n\nFormat each property with:\n- Property name/address\n- Property type\n- Monthly rental price\n- Key location details (nearest MRT, district)\n- Brief description of features\n- Include any image urls \n\nKeep the tone professional yet friendly, and make it easy for users to compare options.\n\n\nUser's search criteria: {{ $('Parse Data').item.json.property.type }} in {{ $('Parse Data').item.json.property.locations }} with budget {{ $('Parse Data').item.json.property.budget }}\nProperty results: {{ $json }} "
        },
        "promptType": "define"
      },
      "typeVersion": 1.9
    },
    {
      "id": "ee223706-e6d3-4891-94e0-3005d7a06a38",
      "name": "格式化响应",
      "type": "n8n-nodes-base.set",
      "position": [
        1480,
        940
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "format-response-field",
              "name": "text",
              "type": "string",
              "value": "={{ $json.output }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "450403d8-776c-4359-890a-9729930c037a",
      "name": "发送格式化响应",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1700,
        840
      ],
      "parameters": {
        "options": {},
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1
    },
    {
      "id": "650470f7-abfa-41f0-8746-947ad102b6a2",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -780,
        880
      ],
      "webhookId": "d22c99cc-a257-4c69-9419-34bb82a14026",
      "parameters": {
        "path": "d22c99cc-a257-4c69-9419-34bb82a14026",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "54ebd3c9-d3a9-46c0-99da-2e3a734459e0",
      "name": "SerpAPI",
      "type": "@n8n/n8n-nodes-langchain.toolSerpApi",
      "position": [
        -20,
        1320
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "295a3776-061c-4324-8639-af282d771072",
      "name": "更新日历",
      "type": "n8n-nodes-base.googleCalendarTool",
      "position": [
        2280,
        1600
      ],
      "parameters": {
        "eventId": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Event_ID', ``, 'string') }}",
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "e3ce404900cffc797cca497fa68efabe30620d0b30b96ff59b64e5b25dfc226b@group.calendar.google.com",
          "cachedResultName": "ABA"
        },
        "operation": "update",
        "updateFields": {}
      },
      "typeVersion": 1.3
    },
    {
      "id": "d39c473d-4ee8-49af-84c9-07e5b2c89fac",
      "name": "OpenAI 聊天模型1",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        1880,
        1600
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini"
        },
        "options": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "f0ad8e3e-6974-4f99-a271-96173bee391d",
      "name": "Gmail1",
      "type": "n8n-nodes-base.gmailTool",
      "position": [
        2420,
        1600
      ],
      "webhookId": "c26a9667-558f-4568-8f1a-59c5c55d58dd",
      "parameters": {
        "sendTo": "={{ $('Gmail Trigger').item.json.from.value[0].address }}",
        "message": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Message', ``, 'string') }}",
        "options": {
          "replyTo": "={{ $('Gmail Trigger').item.json.from.value[0].address }}"
        },
        "subject": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Subject', ``, 'string') }}",
        "emailType": "text"
      },
      "typeVersion": 2.1
    },
    {
      "id": "5157dd53-2ffd-49a5-80e8-893acc8a80bb",
      "name": "AI 代理",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2160,
        1380
      ],
      "parameters": {
        "text": "={{ $json.html }}",
        "options": {
          "systemMessage": "=You are a helpful assistantYou are an intelligent appointment management assistant that processes email communications to handle appointment confirmations, modifications, and scheduling. Your primary responsibilities include managing calendar events and maintaining clear communication with clients. \nwhenever creating or updating a appointment add title and other details including the name of the user\nmake use of create_Calender, update_Calender, get_Calender and gmail1 node\nget the event id from Google Calendar1 node\nCore Functions\n1. Appointment Confirmation Processing\n\nMonitor incoming emails for appointment-related communications\nIdentify confirmation signals such as:\n\n\"Yes, I confirm the appointment\"\n\"The time works for me\"\n\"I'll be there\"\n\"Confirmed\"\nSimilar affirmative responses\n\n\nExtract appointment details including:\n\nDate and time\nDuration\nParticipant information\nMeeting type/purpose\n\n\n\n2. Calendar Management\n\nCreate new appointments when confirmations are received\nUpdate existing appointments with confirmed details\nCheck availability before proposing new times\nMaintain calendar accuracy by updating event statuses\n\n3. Appointment Modification Handling\nWhen emails indicate schedule changes:\n\nParse modification requests such as:\n\n\"Can we reschedule to...\"\n\"I need to move our meeting\"\n\"Is [new time] available?\"\n\"Can we change the appointment to...\"\n\n\nCheck calendar availability for requested new times\nIdentify conflicts and suggest alternative times if needed\nRespond with availability confirmation or alternative options\n\n4. Email Communication Protocol\n\nSend confirmation emails when appointments are successfully scheduled\nRequest clarification when appointment details are unclear\nProvide availability options when requested times conflict\nSend follow-up emails for pending confirmations\nMaintain professional tone in all communications\n\nDecision Logic\nFor Appointment Confirmations:\nIF email contains confirmation language AND appointment details are clear\n  → Update calendar with confirmed appointment\n  → Send confirmation email to participant\nELSE IF confirmation unclear\n  → Request clarification via email\nFor Schedule Change Requests:\nIF email requests time change\n  → Extract proposed new time\n  → Check calendar availability\n  IF time available\n    → Propose confirmation of new time\n    → Wait for final confirmation\n  ELSE\n    → Suggest alternative available times\n    → Wait for selection and confirmation\nFor Ambiguous Communications:\nIF email intent unclear\n  → Send clarifying questions\n  → Wait for response\n  → Process based on clarified intent\nResponse Templates\nConfirmation Success:\n\"Thank you for confirming your appointment on [Date] at [Time]. Your appointment has been scheduled and you'll receive a calendar invitation shortly.\"\nAvailability Check:\n\"I see you'd like to reschedule to [Requested Time]. Let me check availability... [Available Times]. Please confirm which time works best for you.\"\nClarification Request:\n\"I received your message regarding the appointment. Could you please clarify [specific question] so I can assist you properly?\"\nAvailability Conflict:\n\"Unfortunately, [Requested Time] is not available. Here are some alternative times that work: [List Options]. Please let me know which option you prefer.\"\nWorkflow States\nState 1: Monitoring\n\nContinuously monitor incoming emails\nClassify email intent (confirmation, modification, cancellation, inquiry)\n\nState 2: Processing\n\nParse email content for appointment details\nCross-reference with existing calendar data\nDetermine required action\n\nState 3: Action Execution\n\nUpdate calendar as needed\nPrepare appropriate email response\nSend communication to participant\n\nState 4: Awaiting Response\n\nMonitor for follow-up communications\nSet appropriate timeouts for responses\nEscalate unresolved items after reasonable timeframe\n\nError Handling\n\nInvalid dates/times: Request clarification with valid format examples\nCalendar conflicts: Automatically suggest alternatives\nMissing information: Ask specific questions to gather required details\nAmbiguous requests: Break down into specific, answerable questions\n\nSuccess Criteria\n\nAppointments are accurately reflected in calendar\nAll confirmations receive timely responses\nSchedule conflicts are resolved efficiently\nCommunication remains professional and clear\nNo appointments are lost or double-booked\n\nYour goal is to create a seamless appointment management experience that reduces manual intervention while ensuring accuracy and professional communication standards."
        },
        "promptType": "define"
      },
      "typeVersion": 1.9
    },
    {
      "id": "f4e1a9d0-bc00-4ec5-88a7-961f0934d361",
      "name": "Google Calendar",
      "type": "n8n-nodes-base.googleCalendarTool",
      "position": [
        2540,
        1600
      ],
      "parameters": {
        "eventId": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Event_ID', ``, 'string') }}",
        "options": {},
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "e3ce404900cffc797cca497fa68efabe30620d0b30b96ff59b64e5b25dfc226b@group.calendar.google.com",
          "cachedResultName": "ABA"
        },
        "operation": "delete"
      },
      "typeVersion": 1.3
    },
    {
      "id": "d4db75be-8980-4f4d-bf07-66814f9c54fe",
      "name": "Gmail 触发器",
      "type": "n8n-nodes-base.gmailTrigger",
      "position": [
        1860,
        1380
      ],
      "parameters": {
        "simple": false,
        "filters": {},
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyHour"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "7c53569c-f2b9-4f97-a2d4-c235e3336d9a",
      "name": "Google Calendar1",
      "type": "n8n-nodes-base.googleCalendarTool",
      "position": [
        2740,
        1580
      ],
      "parameters": {
        "options": {},
        "timeMax": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Before', `use the appointment end time when it is scheduled`, 'string') }}",
        "timeMin": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('After', `use the appointment start time when it is scheduled`, 'string') }}",
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "e3ce404900cffc797cca497fa68efabe30620d0b30b96ff59b64e5b25dfc226b@group.calendar.google.com",
          "cachedResultName": "ABA"
        },
        "operation": "getAll",
        "returnAll": true
      },
      "typeVersion": 1.3
    },
    {
      "id": "afd24454-84a5-4565-b270-1ee37d3535d6",
      "name": "Gmail",
      "type": "n8n-nodes-base.gmailTool",
      "position": [
        -120,
        1140
      ],
      "webhookId": "c2ab9aa4-e3da-4d5f-a3c1-4e12fcf6600c",
      "parameters": {
        "sendTo": "={{ $fromAI('To', ``, 'string') }}",
        "message": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Message', `Please include the location details of property that appointment has been scheduled.`, 'string') }}",
        "options": {},
        "subject": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Subject', `This is a test mail`, 'string') }}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "695bdf40-f667-423a-b437-bf8af335847a",
      "name": "获取日历",
      "type": "n8n-nodes-base.googleCalendarTool",
      "position": [
        100,
        1220
      ],
      "parameters": {
        "options": {},
        "timeMax": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Before', `Three days after the \"after\"`, 'string') }}",
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "e3ce404900cffc797cca497fa68efabe30620d0b30b96ff59b64e5b25dfc226b@group.calendar.google.com",
          "cachedResultName": "ABA"
        },
        "operation": "getAll",
        "returnAll": true
      },
      "typeVersion": 1.3
    },
    {
      "id": "1eb80e21-84bc-4b81-a21d-669c90837a24",
      "name": "创建日历1",
      "type": "n8n-nodes-base.googleCalendarTool",
      "position": [
        2100,
        1620
      ],
      "parameters": {
        "end": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('End', `end the event an hour after the start of the event`, 'string') }}",
        "start": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Start', `start an event at the time of appointment scheduled in indian standard time`, 'string') }}",
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "e3ce404900cffc797cca497fa68efabe30620d0b30b96ff59b64e5b25dfc226b@group.calendar.google.com",
          "cachedResultName": "ABA"
        },
        "descriptionType": "manual",
        "toolDescription": "create an event after the confirmation mail from the user, add title description about the property and user details and return the event id to the agent",
        "additionalFields": {
          "color": "7",
          "summary": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Summary', `with buyer name`, 'string') }}",
          "location": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Location', ``, 'string') }}"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "17fd2bbb-8cd9-4746-834e-c0680c390bc2",
      "name": "人工客服",
      "type": "n8n-nodes-base.gmailTool",
      "position": [
        180,
        1080
      ],
      "webhookId": "5e50135f-6164-49f3-901e-661a8c0486a6",
      "parameters": {
        "sendTo": "markkevin109@gmail.com",
        "message": "=<b>Dear Mark,</b><br><br>\n\nI hope this email finds you well.<br><br>\n\nCustomer Details:<br>\n- Name: <b>{{ $fromAI('customer_name', 'Customer Name', 'string') }}</b><br>\n- Phone: <b>{{ $fromAI('customer_phone', 'Phone Number', 'string') }}</b><br>\n- Email:  <b>{{ $fromAI('customer_email', 'Email Address', 'string') }}</b><br><br>\n\nCustomer Message:<br>\n<b>{{ $fromAI('customer_message', 'Customer Message', 'string') }}</b><br><br>\n\nProperty Requirements (if any collected):<br>\n<b>{{ $fromAI('property_requirements', 'None specified', 'string') }}</b><br><br>\n\n<b>Please let me know if you need any further information or clarification.</b><br><br>\n\n<b>Best regards</b>",
        "options": {},
        "subject": "={{ $fromAI('subject', 'Customer Handoff Request', 'string') }}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "e52b0389-eb41-4d9f-8dc8-9434e1957886",
      "name": "代码",
      "type": "n8n-nodes-base.code",
      "position": [
        -540,
        880
      ],
      "parameters": {
        "jsCode": "const input = $input.first();\nconst message = input.json.body?.message || input.json.message || \"\";\n\n// Simple URL regex\nconst urlRegex = /(https?:\\/\\/[^\\s]+)/g;\n\n// Extract links\nconst foundLinks = message.match(urlRegex);\n\nreturn [\n  {\n    json: {\n      originalMessage: message,\n      link: foundLinks ? foundLinks[0] : null, // Return the first found link\n      allLinks: foundLinks || [],\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "89b6f6be-f3aa-4981-8121-c8f4650e863c",
      "name": "如果",
      "type": "n8n-nodes-base.if",
      "position": [
        -400,
        680
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "db9f1cb5-db2a-448d-82f0-6e5449d36411",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.link }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "8ea7c7bb-5692-4ef8-a08d-5baa0656ecb4",
      "name": "Postgres",
      "type": "n8n-nodes-base.postgres",
      "position": [
        -80,
        660
      ],
      "parameters": {
        "query": "SELECT *\nFROM new_properties\nWHERE propertyguru_url = $1 OR \"99.co_url\" = $1\nLIMIT 1;\n",
        "options": {
          "queryReplacement": "={{ $json.link }}"
        },
        "operation": "executeQuery"
      },
      "credentials": {
        "postgres": {
          "id": "Y3Im9kfoKd4hu5NL",
          "name": "Postgres letchu"
        }
      },
      "typeVersion": 2.6
    },
    {
      "id": "1bc9824a-6d3c-47d5-ae61-74acfa78d167",
      "name": "代码1",
      "type": "n8n-nodes-base.code",
      "position": [
        460,
        660
      ],
      "parameters": {
        "jsCode": "const properties = $input.all().map((item) => item.json);\n\nconst propertySummaries = properties.map((property) => {\n  const {\n    \"Property Type\": propertyType,\n    \"Condo Name\": condoName,\n    \"Floor Level\": floorLevel,\n    \"Built Year\": builtYear,\n    \"Floor Area (sqft)\": floorArea,\n    Bedrooms: bedrooms,\n    Bathrooms: bathrooms,\n    \"Maid Room Available\": maidRoomAvailable,\n    \"Furnishing Status\": furnishingStatus,\n    Availability: availability,\n    \"Agency Fee Applicable\": agencyFeeApplicable,\n    \"Rental Price (SGD/month)\": rentalPrice,\n    \"Minimum Lease Period\": minimumLeasePeriod,\n    \"Security Deposit\": securityDeposit,\n    Street: street,\n    Level: level,\n    \"Unit No\": unitNo,\n    \"Postal Code\": postalCode,\n    City: city,\n    Country: country,\n    District: district,\n    \"Nearest MRT\": nearestMRT,\n    \"Distance from MRT\": distanceFromMRT,\n    \"MRT Line\": mrtLine,\n    \"Nearest Bus Stop\": nearestBusStop,\n    \"Nearest School\": nearestSchool,\n    \"Nearest University\": nearestUniversity,\n    \"Nearest Convenience Store\": nearestConvenienceStore,\n    Aircon: aircon,\n    Balcony: balcony,\n    Renovated: renovated,\n    \"Pet Friendly\": petFriendly,\n    \"Smoking Allowed\": smokingAllowed,\n    \"Cooking Allowed\": cookingAllowed,\n    Facing: facing,\n    \"BBQ Pits\": bbqPits,\n    Clubhouse: clubhouse,\n    Gym: gym,\n    \"Fitness Corner\": fitnessCorner,\n    Jacuzzi: jacuzzi,\n    \"Jogging Track\": joggingTrack,\n    \"Swimming Pool\": swimmingPool,\n    \"Gender Preferred\": genderPreferred,\n    \"Nationality Preference\": nationalityPreference,\n    \"Cooking Preference\": cookingPreference,\n    \"Visitors Allowed\": visitorsAllowed,\n    \"Group Size\": groupSize,\n    \"Preferred Profile\": preferredProfile,\n  } = property;\n\n  return `The property is a ${propertyType} named ${condoName} built in ${builtYear}. It is located at ${street}, ${city}, ${country} with postal code ${postalCode}. The condo is on the ${floorLevel} with a unit number ${unitNo}. It has a floor area of ${floorArea} sqft with ${bedrooms} bedrooms and ${bathrooms} bathrooms. Maid room is ${maidRoomAvailable ? \"available\" : \"not available\"}. The property is ${furnishingStatus} and is currently ${availability}. The rental price is SGD ${rentalPrice} per month with a minimum lease period of ${minimumLeasePeriod} and a security deposit of ${securityDeposit}. The property is ${renovated ? \"renovated\" : \"not renovated\"} with ${aircon ? \"air conditioning\" : \"no air conditioning\"} and ${balcony ? \"a balcony\" : \"no balcony\"}. The property is ${petFriendly ? \"pet friendly\" : \"not pet friendly\"} and ${smokingAllowed ? \"allows smoking\" : \"does not allow smoking\"}. Cooking is ${cookingAllowed ? \"allowed\" : \"not allowed\"} and the property faces ${facing}. The property has ${bbqPits ? \"BBQ pits\" : \"no BBQ pits\"}, ${clubhouse ? \"a clubhouse\" : \"no clubhouse\"}, ${gym ? \"a gym\" : \"no gym\"}, ${fitnessCorner ? \"a fitness corner\" : \"no fitness corner\"}, ${jacuzzi ? \"a jacuzzi\" : \"no jacuzzi\"}, ${joggingTrack ? \"a jogging track\" : \"no jogging track\"}, and ${swimmingPool ? \"a swimming pool\" : \"no swimming pool\"}. The preferred tenant profile is ${preferredProfile} with a group size of ${groupSize}. Visitors are ${visitorsAllowed ? \"allowed\" : \"not allowed\"}. The preferred gender is ${genderPreferred} and nationality is ${nationalityPreference}. Cooking preference is ${cookingPreference}. The nearest MRT station is ${nearestMRT} which is ${distanceFromMRT} away. The MRT line is ${mrtLine}. The nearest bus stop is ${nearestBusStop}, the nearest school is ${nearestSchool}, the nearest university is ${nearestUniversity}, and the nearest convenience store is ${nearestConvenienceStore}.`;\n});\n\nreturn { propertySummaries };\n"
      },
      "typeVersion": 2
    },
    {
      "id": "9aea30af-b6be-45aa-967f-58b9a0c24e62",
      "name": "获取属性媒体",
      "type": "n8n-nodes-base.postgres",
      "position": [
        540,
        1260
      ],
      "parameters": {
        "query": "SELECT storage_path,property_id\nFROM property_media\nWHERE property_id = $1\nLIMIT 1;\n",
        "options": {
          "queryReplacement": "={{ $json.ID }}"
        },
        "operation": "executeQuery"
      },
      "typeVersion": 2.6
    },
    {
      "id": "d880d83c-1173-4cfd-9202-d38314090639",
      "name": "构建图像URL",
      "type": "n8n-nodes-base.code",
      "position": [
        760,
        1200
      ],
      "parameters": {
        "jsCode": "const baseUrl = 'https://ztsgwqqxffrvqoqwrsjn.supabase.co/storage/v1/object/public/propertyimages/';\n\n// Use a Set to deduplicate, then take the first one\nconst urls = [...new Set(items.map(item => baseUrl + item.json.storage_path))];\n\nreturn [\n  {\n    json: {\n      image_url: urls[0] || null,  // If no image found, return null\n      id:$input.first().json.property_id\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "cb0d7cb8-2a83-43f2-a992-3b75ce0c4fc4",
      "name": "合并",
      "type": "n8n-nodes-base.merge",
      "position": [
        960,
        1040
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "advanced": true,
        "joinMode": "keepEverything",
        "mergeByFields": {
          "values": [
            {
              "field1": "ID",
              "field2": "id"
            }
          ]
        }
      },
      "typeVersion": 3.1
    },
    {
      "id": "9d7a7def-ac90-4c44-a621-9740336bd7da",
      "name": "条件判断1",
      "type": "n8n-nodes-base.if",
      "position": [
        140,
        640
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "81840824-4572-4acf-afe6-f684fe9e72fe",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.Availability }}",
              "rightValue": "Immediate"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "56c5c116-c26d-4fff-8854-9b0808602716",
      "name": "OpenAI 聊天模型3",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        1120,
        1160
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini"
        },
        "options": {
          "temperature": 0
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "bdd306d9-d719-44d1-829c-68259d3fe461",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1300,
        -80
      ],
      "parameters": {
        "width": 460,
        "height": 1980,
        "content": "#### 描述"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "b83b3021-8e5a-4965-a71f-7ddf623463d1",
  "connections": {
    "If": {
      "main": [
        [
          {
            "node": "Postgres",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "LLM conversation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If1": {
      "main": [
        [
          {
            "node": "Code1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "LLM conversation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code1": {
      "main": [
        [
          {
            "node": "Send Formatted Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail": {
      "ai_tool": [
        [
          {
            "node": "LLM conversation",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Format Results with AI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail1": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Webhook": {
      "main": [
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Postgres": {
      "main": [
        [
          {
            "node": "If1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Data": {
      "main": [
        [
          {
            "node": "Ready Check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Human agent": {
      "ai_tool": [
        [
          {
            "node": "LLM conversation",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Ready Check": {
      "main": [
        [
          {
            "node": "Query Properties",
            "type": "main",
            "index": 0
          },
          {
            "node": "Save Personal Info",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "get_Calendar": {
      "ai_tool": [
        [
          {
            "node": "LLM conversation",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Gmail Trigger": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Response": {
      "main": [
        [
          {
            "node": "Send Formatted Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Calendar": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "update_Calendar": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Google Calendar1": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "LLM conversation": {
      "main": [
        [
          {
            "node": "Parse Data",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send Formatted Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Query Properties": {
      "main": [
        [
          {
            "node": "Get Property Media",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "create_Calendar1": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "LLM conversation",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "PostgreSQL Memory": {
      "ai_memory": [
        [
          {
            "node": "LLM conversation",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Get Property Media": {
      "main": [
        [
          {
            "node": "construct_image_url",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model3": {
      "ai_languageModel": [
        [
          {
            "node": "Format Results with AI",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "construct_image_url": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Format Results with AI": {
      "main": [
        [
          {
            "node": "Format Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

高级 - 客户培育, 多模态 AI

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
高级
节点数量33
分类2
节点类型15
难度说明

适合高级用户,包含 16+ 个节点的复杂工作流

外部链接
在 n8n.io 查看

分享此工作流