{
  "id": "NCOZlBqu3NuuQ8Pb",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Automate Hotel Guest Upsells with AI using OpenAI, Sheets & Slack",
  "tags": [],
  "nodes": [
    {
      "id": "e75a2d38-6cb7-4512-bc09-f9b260ade174",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -800,
        304
      ],
      "parameters": {
        "color": 7,
        "width": 492,
        "height": 400,
        "content": "## \u26a0\ufe0f Error Monitoring\n\nCatches any workflow failures and sends alerts to Slack's general-information channel. Helps maintain reliability and enables quick troubleshooting."
      },
      "typeVersion": 1
    },
    {
      "id": "5201a5a0-190c-4be7-9c93-bea85bfae970",
      "name": "Error Trigger",
      "type": "n8n-nodes-base.errorTrigger",
      "position": [
        -736,
        496
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "c8ddf38b-c202-4f62-9d18-09ccb3202f96",
      "name": "Alert on Workflow Failure",
      "type": "n8n-nodes-base.slack",
      "position": [
        -480,
        496
      ],
      "parameters": {
        "text": "\u26a0\ufe0f Hotel Pre-Arrival Workflow Error Detected\n\nPlease check the execution log for details.",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C09GNB90TED",
          "cachedResultName": "workflow-errors"
        },
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.4
    },
    {
      "id": "b6cca306-1f7a-45b8-8039-fab9c8db91c8",
      "name": "Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1376,
        -912
      ],
      "parameters": {
        "width": 581,
        "height": 465,
        "content": "## \ud83c\udfe8 Automate Hotel Guest Upsells with AI using OpenAI, Sheets & Slack\n\n### How it works\nThis workflow reads guest data from Google Sheets daily at 9 AM, categorizes guests by stay status (before arrival or currently checked in), and uses AI to generate personalized upsell recommendations. The system suggests room upgrades or airport pickup for upcoming guests, and spa, dining, or experiences for current guests. Results are written back to the spreadsheet and posted to Slack for team visibility.\n\n### Setup steps\n1. Connect Google Sheets OAuth2 credentials and point to your guest spreadsheet\n2. Add OpenAI API credentials for GPT-4o-mini\n3. Configure Slack credentials and select your notification channel\n4. Ensure your spreadsheet has columns: Guest Name, Email, Stay Status, Room Type, Repeat Guest, Spend Level, Preferences, Special Occasion, upsell_type\n5. Test with a manual trigger before activating the daily schedule"
      },
      "typeVersion": 1
    },
    {
      "id": "412568b2-011f-4c0e-a75c-a6fc352dfd4e",
      "name": "Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -752,
        -336
      ],
      "parameters": {
        "color": 7,
        "width": 413,
        "height": 365,
        "content": "## \ud83d\udce5 Data Collection & Filtering\n\nRetrieves all guest records from Google Sheets and splits them into two paths based on stay status: guests arriving soon vs. currently checked in."
      },
      "typeVersion": 1
    },
    {
      "id": "0b19920e-5484-427a-b0d0-7cc58ecb039b",
      "name": "Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -288,
        -496
      ],
      "parameters": {
        "color": 7,
        "width": 509,
        "height": 717,
        "content": "## \ud83c\udfaf Guest Context Preparation\n\nExtracts relevant guest attributes (name, room type, preferences, spending level, occasion) and adds stay phase context for AI processing."
      },
      "typeVersion": 1
    },
    {
      "id": "603133eb-9ec2-4010-80cb-571db35681a0",
      "name": "Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        256,
        -448
      ],
      "parameters": {
        "color": 7,
        "width": 485,
        "height": 601,
        "content": "## \ud83e\udd16 AI Upsell Generation\n\nUses OpenAI to analyze guest profiles and recommend the single best upsell opportunity. Parses JSON response to extract offer type, personalized message, and reasoning."
      },
      "typeVersion": 1
    },
    {
      "id": "4ad66e6d-5386-4117-a77f-e88fc709a198",
      "name": "Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        800,
        -336
      ],
      "parameters": {
        "color": 7,
        "width": 453,
        "height": 457,
        "content": "## \ud83d\udcbe Update & Notify\n\nWrites the AI-generated upsell type back to the spreadsheet row and posts a formatted notification to Slack with guest details and offer reasoning."
      },
      "typeVersion": 1
    },
    {
      "id": "76433870-e71c-4ab6-8d52-16231736b7e2",
      "name": "Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1296,
        -112
      ],
      "parameters": {
        "color": 3,
        "width": 308.92307692307696,
        "height": 228.8615384615385,
        "content": "## \ud83d\udd10 Credentials Required\n\n**Google Sheets:** OAuth2 with read/write permissions  \n**OpenAI:** API key with GPT-4o-mini access  \n**Slack:** App token with chat:write scope\n\nReplace all credential IDs with your own before deploying."
      },
      "typeVersion": 1
    },
    {
      "id": "f4896e43-6daf-4003-a35b-dcd65ae71407",
      "name": "Schedule Trigger1",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -672,
        -160
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * *"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "a620855b-afd3-4fd5-ab61-cb7d511115a1",
      "name": "Google Sheets - Read Guests1",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -464,
        -160
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 1525755670,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/17rcNd_ZpUQLm0uWEVbD-NY6GyFUkrD4BglvawlyBygM/edit#gid=1525755670",
          "cachedResultName": "guest"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "17rcNd_ZpUQLm0uWEVbD-NY6GyFUkrD4BglvawlyBygM",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/17rcNd_ZpUQLm0uWEVbD-NY6GyFUkrD4BglvawlyBygM/edit?usp=drivesdk",
          "cachedResultName": "sample_leads_50"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "45e8e114-7c4f-4e95-a5fe-6baab06037b1",
      "name": "IF - Before Arrival1",
      "type": "n8n-nodes-base.if",
      "position": [
        -208,
        -240
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "path-before-arrival",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json['Stay Status'] }}",
              "rightValue": "upcoming"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "2c0a027e-8dfb-4762-b3dc-52ba555085bc",
      "name": "IF - During Stay1",
      "type": "n8n-nodes-base.if",
      "position": [
        -208,
        -32
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "path-during-stay",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json['Stay Status'] }}",
              "rightValue": "checked_in"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "45f007e2-c271-4c43-8b1d-97144cfad522",
      "name": "Set - Guest Context (Before)1",
      "type": "n8n-nodes-base.set",
      "position": [
        64,
        -240
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "guest-name",
              "name": "guest_name",
              "type": "string",
              "value": "={{ $json['Guest Name'] }}"
            },
            {
              "id": "room-type",
              "name": "room_type",
              "type": "string",
              "value": "={{ $json['Room Type'] }}"
            },
            {
              "id": "repeat-guest",
              "name": "repeat_guest",
              "type": "string",
              "value": "={{ $json['Repeat Guest'] }}"
            },
            {
              "id": "spend-level",
              "name": "spend_level",
              "type": "string",
              "value": "={{ $json['Spend Level'] }}"
            },
            {
              "id": "preferences",
              "name": "preferences",
              "type": "string",
              "value": "={{ $json['Preferences'] }}"
            },
            {
              "id": "occasion",
              "name": "occasion",
              "type": "string",
              "value": "={{ $json['Special Occasion'] }}"
            },
            {
              "id": "stay-phase",
              "name": "stay_phase",
              "type": "string",
              "value": "before_arrival"
            },
            {
              "id": "email",
              "name": "email",
              "type": "string",
              "value": "={{ $json['Email'] }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "ee71d128-47d6-4487-bad5-4a1abcac2550",
      "name": "Set - Guest Context (During)1",
      "type": "n8n-nodes-base.set",
      "position": [
        64,
        -32
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "guest-name",
              "name": "guest_name",
              "type": "string",
              "value": "={{ $json['Guest Name'] }}"
            },
            {
              "id": "room-type",
              "name": "room_type",
              "type": "string",
              "value": "={{ $json['Room Type'] }}"
            },
            {
              "id": "repeat-guest",
              "name": "repeat_guest",
              "type": "string",
              "value": "={{ $json['Repeat Guest'] }}"
            },
            {
              "id": "spend-level",
              "name": "spend_level",
              "type": "string",
              "value": "={{ $json['Spend Level'] }}"
            },
            {
              "id": "preferences",
              "name": "preferences",
              "type": "string",
              "value": "={{ $json['Preferences'] }}"
            },
            {
              "id": "occasion",
              "name": "occasion",
              "type": "string",
              "value": "={{ $json['Special Occasion'] }}"
            },
            {
              "id": "stay-phase",
              "name": "stay_phase",
              "type": "string",
              "value": "during_stay"
            },
            {
              "id": "email",
              "name": "email",
              "type": "string",
              "value": "={{ $json['Email'] }}"
            },
            {
              "id": "row-number",
              "name": "row_number",
              "type": "number",
              "value": "={{ $json['__rowNumber'] }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "497b1353-f2a0-4acd-b88d-84b374eed0ec",
      "name": "AI - Generate Upsell1",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        304,
        -128
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "GPT-4O-MINI"
        },
        "options": {
          "maxTokens": 300,
          "temperature": 0.7
        },
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You are a hotel revenue assistant. Recommend ONE best upsell offer per guest. You must respond ONLY with valid JSON in this exact format: {\"upsell_type\": \"string\", \"offer_message\": \"string\", \"reason\": \"string\"}. Do not include any markdown, explanations, or other text."
            },
            {
              "content": "=Guest Profile:\n- Name: {{ $json.guest_name }}\n- Room Type: {{ $json.room_type }}\n- Repeat Guest: {{ $json.repeat_guest }}\n- Spend Level: {{ $json.spend_level }}\n- Preferences: {{ $json.preferences }}\n- Special Occasion: {{ $json.occasion }}\n- Stay Phase: {{ $json.stay_phase }}\n\nRules:\n- If stay_phase is \"before_arrival\": recommend room upgrade or airport pickup\n- If stay_phase is \"during_stay\": recommend spa, dining, or experience\n- Choose only ONE upsell that best matches the guest profile\n- Consider spend level and preferences\n- Make the offer personalized and compelling\n\nRespond with JSON only."
            }
          ]
        }
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.7
    },
    {
      "id": "904506eb-1031-4d6d-a1a4-f21a186b329b",
      "name": "Code - Parse AI Response1",
      "type": "n8n-nodes-base.code",
      "position": [
        624,
        -128
      ],
      "parameters": {
        "jsCode": "const items = [];\n\nfor (const item of $input.all()) {\n  let aiResponse = item.json.message?.content || item.json.text || '';\n  \n  aiResponse = aiResponse.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n  \n  let upsellData;\n  try {\n    upsellData = JSON.parse(aiResponse);\n  } catch (error) {\n    upsellData = {\n      upsell_type: 'Error',\n      offer_message: 'Unable to generate offer',\n      reason: 'AI response parsing failed'\n    };\n  }\n  \n  items.push({\n    json: {\n      guest_name: item.json.guest_name,\n      email: item.json.email,\n      room_type: item.json.room_type,\n      stay_phase: item.json.stay_phase,\n      row_number: item.json.row_number,\n      upsell_type: upsellData.upsell_type || 'Unknown',\n      offer_message: upsellData.offer_message || '',\n      reason: upsellData.reason || ''\n    },\n    pairedItem: item.pairedItem\n  });\n}\n\nreturn items;"
      },
      "typeVersion": 2
    },
    {
      "id": "944add7d-4179-4107-860f-423dc5f123d5",
      "name": "Google Sheets - Update Row1",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        848,
        -128
      ],
      "parameters": {
        "columns": {
          "value": {
            "row_number": "={{ $('Google Sheets - Read Guests1').item.json.row_number }}",
            "upsell_type": "={{ $json.upsell_type }}"
          },
          "schema": [
            {
              "id": "Guest Name",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Guest Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Email",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Check-in Date",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Check-in Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Stay Status",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Stay Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Room Type",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Room Type",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Repeat Guest",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Repeat Guest",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Spend Level",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Spend Level",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Preferences",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Preferences",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Special Occasion",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Special Occasion",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "upsell_type",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "upsell_type",
              "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": [
            "row_number"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 1525755670,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/17rcNd_ZpUQLm0uWEVbD-NY6GyFUkrD4BglvawlyBygM/edit#gid=1525755670",
          "cachedResultName": "guest"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "17rcNd_ZpUQLm0uWEVbD-NY6GyFUkrD4BglvawlyBygM",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/17rcNd_ZpUQLm0uWEVbD-NY6GyFUkrD4BglvawlyBygM/edit?usp=drivesdk",
          "cachedResultName": "sample_leads_50"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "b7cc0282-83b8-4fe4-b4dc-8a9ddf0e1e17",
      "name": "Slack - Notify Team1",
      "type": "n8n-nodes-base.slack",
      "position": [
        1072,
        -128
      ],
      "parameters": {
        "text": "=\ud83c\udfe8 *New Upsell Opportunity*\n\n*Guest:* {{ $('Google Sheets - Read Guests1').item.json['Guest Name'] }}\n*Upsell Type:* {{ $json.upsell_type }}\n\n*Offer Message:*\n{{ $('Code - Parse AI Response1').item.json.offer_message }}\n\n*Reason:*\n{{ $('Code - Parse AI Response1').item.json.reason }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C09GNB90TED",
          "cachedResultName": "general-information"
        },
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "e875c187-e6f2-4df7-b73b-9761c461917f",
  "connections": {
    "Error Trigger": {
      "main": [
        [
          {
            "node": "Alert on Workflow Failure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF - During Stay1": {
      "main": [
        [
          {
            "node": "Set - Guest Context (During)1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger1": {
      "main": [
        [
          {
            "node": "Google Sheets - Read Guests1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF - Before Arrival1": {
      "main": [
        [
          {
            "node": "Set - Guest Context (Before)1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI - Generate Upsell1": {
      "main": [
        [
          {
            "node": "Code - Parse AI Response1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code - Parse AI Response1": {
      "main": [
        [
          {
            "node": "Google Sheets - Update Row1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Sheets - Update Row1": {
      "main": [
        [
          {
            "node": "Slack - Notify Team1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Sheets - Read Guests1": {
      "main": [
        [
          {
            "node": "IF - Before Arrival1",
            "type": "main",
            "index": 0
          },
          {
            "node": "IF - During Stay1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set - Guest Context (Before)1": {
      "main": [
        [
          {
            "node": "AI - Generate Upsell1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set - Guest Context (During)1": {
      "main": [
        [
          {
            "node": "AI - Generate Upsell1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}