This workflow corresponds to n8n.io template #10578 — we link there as the canonical source.
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 →
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "a61671f5-447d-448c-8b56-bf68c3c1b6ee",
"name": "IF (Guardrail Check)",
"type": "n8n-nodes-base.if",
"position": [
-1680,
624
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "check-aligned",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "={{ $json.checks[0].triggered }}",
"rightValue": ""
},
{
"id": "check-nsfw",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "={{ $json.checks[1].triggered }}",
"rightValue": ""
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "23a9e386-0d4c-44ac-a266-544d37b3f7ce",
"name": "Merge Sanitized Data",
"type": "n8n-nodes-base.set",
"position": [
-416,
720
],
"parameters": {
"values": {
"string": [
{
"name": "special_requests",
"value": "={{ $('Guardrails').item.json.sanitized_text }}"
}
]
},
"options": {}
},
"typeVersion": 1
},
{
"id": "e1218ec7-0b51-40ef-adb9-c49f82eed024",
"name": "Set Invalid Email Error",
"type": "n8n-nodes-base.set",
"position": [
-1600,
256
],
"parameters": {
"values": {
"string": [
{
"name": "error_type",
"value": "INVALID_EMAIL_TOPIC"
},
{
"name": "error_message",
"value": "={{ $json.is_aligned === false ? \"Email is off-topic.\" : \"Email contains NSFW content.\" }}"
},
{
"name": "timestamp",
"value": "={{ new Date().toISOString() }}"
},
{
"name": "original_subject",
"value": "={{ $('Get many messages (1)').item.json.subject }}"
},
{
"name": "original_sender",
"value": "={{ $('Get many messages (1)').item.json.from.value[0].address }}"
},
{
"name": "email_snippet",
"value": "={{ $('Look for incoming emails').item.json.snippet }}"
},
{
"name": "workflow_execution_id",
"value": "={{ $execution.id }}"
}
]
},
"options": {}
},
"typeVersion": 1
},
{
"id": "9c278e2e-160a-48d8-8b08-053ee914d4a3",
"name": "Log Invalid Email Error",
"type": "n8n-nodes-base.googleSheets",
"position": [
-1312,
256
],
"parameters": {
"columns": {
"value": {
"timestamp": "={{ $json.timestamp }}",
"error_type": "={{ $json.error_type }}",
"email_snippet": "={{ $json.email_snippet }}",
"error_message": "={{ $json.error_message }}",
"original_sender": "={{ $json.original_sender }}",
"original_subject": "={{ $json.original_subject }}",
"workflow_execution_id": "={{ $json.workflow_execution_id }}"
},
"schema": [
{
"id": "timestamp",
"type": "string",
"displayName": "timestamp"
},
{
"id": "error_type",
"type": "string",
"displayName": "error_type"
},
{
"id": "error_message",
"type": "string",
"displayName": "error_message"
},
{
"id": "original_subject",
"type": "string",
"displayName": "original_subject"
},
{
"id": "original_sender",
"type": "string",
"displayName": "original_sender"
},
{
"id": "email_snippet",
"type": "string",
"displayName": "email_snippet"
},
{
"id": "extracted_data",
"type": "string",
"displayName": "extracted_data"
},
{
"id": "workflow_execution_id",
"type": "string",
"displayName": "workflow_execution_id"
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "Error Logs",
"cachedResultName": "Error Logs"
},
"documentId": {
"__rl": true,
"mode": "expression",
"value": "={{ $('Configuration: User Settings').item.json.gSheetID }}"
}
},
"retryOnFail": true,
"typeVersion": 4.5,
"waitBetweenTries": 2000
},
{
"id": "038fcd9d-afe8-4b1d-be58-09a6bb105deb",
"name": "Send Invalid Email Notification",
"type": "n8n-nodes-base.gmail",
"position": [
-1008,
256
],
"parameters": {
"sendTo": "={{ $('Configuration: User Settings').item.json.adminEmailForErrors }}",
"message": "=<html>\n<body style=\"font-family: Arial, sans-serif; line-height: 1.6; color: #333;\">\n\t<div style=\"max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #dc3545; border-radius: 5px;\">\n\t\t<h2 style=\"color: #dc3545; border-bottom: 2px solid #dc3545; padding-bottom: 10px;\">\u26a0\ufe0f Invalid Email Detected</h2>\n\t\t\n\t\t<p>The workflow detected an email that failed the initial guardrail checks and was not processed.</p>\n\t\t\n\t\t<div style=\"background-color: #f8d7da; padding: 15px; border-radius: 5px; margin: 20px 0; border-left: 4px solid #dc3545;\">\n\t\t\t<h3 style=\"color: #721c24; margin-top: 0;\">Error Details</h3>\n\t\t\t<p><strong>Error Type:</strong> {{ $json.error_type }}</p>\n\t\t\t<p><strong>Error Message:</strong> {{ $json.error_message }}</p>\n\t\t\t<p><strong>Timestamp:</strong> {{ $json.timestamp }}</p>\n\t\t</div>\n\t\t\n\t\t<div style=\"background-color: #fff3cd; padding: 15px; border-radius: 5px; margin: 20px 0; border-left: 4px solid #ffc107;\">\n\t\t\t<h3 style=\"color: #856404; margin-top: 0;\">Original Email Information</h3>\n\t\t\t<p><strong>From:</strong> {{ $json.original_sender || 'N/A' }}</p>\n\t\t\t<p><strong>Subject:</strong> {{ $json.original_subject || 'N/A' }}</p>\n\t\t\t<p><strong>Snippet:</strong> {{ $json.email_snippet || 'N/A' }}</p>\n\t\t</div>\n\t\t\n\t\t<div style=\"margin-top: 20px; padding: 15px; background-color: #e7f3ff; border-radius: 5px;\">\n\t\t\t<h3 style=\"color: #004085; margin-top: 0;\">Action Required</h3>\n\t\t\t<p>Please review this email in the inbox manually to see if it requires any action. It has been ignored by the automation.</p>\n\t\t\t<p><strong>Workflow Execution ID:</strong> {{ $execution.id }}</p>\n\t\t</div>\n\t\t\n\t\t<p style=\"margin-top: 30px; font-size: 12px; color: #666;\">This is an automated alert from the Hotel Booking Workflow System.</p>\n\t</div>\n</body>\n</html>",
"options": {
"appendAttribution": false
},
"subject": "=\u26a0\ufe0f Invalid Email Received - {{ $json.error_type }}"
},
"typeVersion": 2.1
},
{
"id": "a1bd31b5-fcb3-46d5-b539-53a8b272c9b6",
"name": "OpenAI Model for Email Text2",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
-1760,
1424
],
"parameters": {
"text": "={{ $('Get many messages (1)').item.json.text }}",
"options": {
"systemMessage": "=You are a hotel booking specialist AI. Extract booking information from emails and return ONLY a valid JSON object.\n\nCRITICAL INSTRUCTIONS:\n1. Look for booking details anywhere in the email text\n2. Extract numbers from phrases like \"55 rooms\" or \"Number of Rooms: 55\"\n3. Parse dates in any format and convert to YYYY-MM-DD\n4. If a field is not found, use null\n5. Return ONLY the JSON object - no explanations\n6. If the users provide their credit card as a special request. In the special request dont parse the credit card numbers, write [CREDIT_CARD] instead.\n\n\nREQUIRED JSON STRUCTURE:\n{\n \"travel_agency_name\": \"string or null\",\n \"contact_person\": \"string or null\",\n \"contact_email\": \"string or null\",\n \"contact_phone\": \"string or null\",\n \"number_of_rooms\": number,\n \"check_in_date\": \"YYYY-MM-DD or null\",\n \"check_out_date\": \"YYYY-MM-DD or null\",\n \"room_type\": \"string or null\",\n \"special_requests\": \"string or null\",\n \"total_guests\": number,\n \"booking_reference\": \"string or null\",\n \"urgency\": \"urgent or normal or low\"\n}"
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 1.9,
"continueOnFail": true
},
{
"id": "4b843e31-9ae1-4b72-8139-46ce8c6944cd",
"name": "Guardrails",
"type": "@n8n/n8n-nodes-langchain.guardrails",
"position": [
-608,
944
],
"parameters": {
"text": "={{ $json.special_requests }}",
"operation": "sanitize",
"guardrails": {
"pii": {
"value": {
"type": "selected",
"entities": [
"CREDIT_CARD"
]
}
}
}
},
"typeVersion": 1
},
{
"id": "64d82eb3-1975-46c2-8526-a7a74cad009e",
"name": "OpenAI Model for PDF",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
-1760,
1136
],
"parameters": {
"text": "={{ $json.text }}",
"options": {
"systemMessage": "=You are a hotel booking specialist AI. Extract booking information from the PDF attachment and return ONLY a valid JSON object.\n\nCRITICAL INSTRUCTIONS:\n1. Look for booking details anywhere in the document\n2. Extract numbers from phrases like \"55 rooms\" or \"Number of Rooms: 55\"\n3. Parse dates in any format and convert to YYYY-MM-DD\n4. If a field is not found, use null\n5. Return ONLY the JSON object - no explanations\n6. If the users provide their credit card as a special request. In the special request dont parse the credit card numbers, write [CREDIT_CARD] instead.\n\nREQUIRED JSON STRUCTURE:\n{\n \"travel_agency_name\": \"string or null\",\n \"contact_person\": \"string or null\",\n \"contact_email\": \"string or null\",\n \"contact_phone\": \"string or null\",\n \"number_of_rooms\": number,\n \"check_in_date\": \"YYYY-MM-DD or null\",\n \"check_out_date\": \"YYYY-MM-DD or null\",\n \"room_type\": \"string or null\",\n \"special_requests\": \"string or null\",\n \"total_guests\": number,\n \"booking_reference\": \"string or null\",\n \"urgency\": \"urgent or normal or low\"\n}"
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 1.9,
"continueOnFail": true
},
{
"id": "2da3c261-1a25-4891-a8ae-286a799d4eab",
"name": "Filter Booking Emails",
"type": "n8n-nodes-base.filter",
"position": [
-1264,
704
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "or",
"conditions": [
{
"id": "filter-booking-subject",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $('Look for incoming emails').item.json.Subject }}",
"rightValue": "Booking Request"
},
{
"id": "107664b3-31f6-4df6-89ef-33fc5af78cce",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $('Look for incoming emails').item.json.Subject }}",
"rightValue": "reservation"
},
{
"id": "4de37c49-313a-4477-80cd-9553fde62f53",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $('Look for incoming emails').item.json.Subject }}",
"rightValue": "room request"
},
{
"id": "839bc665-ad8b-469c-862c-d911418d66ae",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $('Look for incoming emails').item.json.Subject }}",
"rightValue": "accommondation"
},
{
"id": "ec0fc23d-1083-4dac-a9ea-4c2a6eedb740",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $('Look for incoming emails').item.json.Subject }}",
"rightValue": "check-in"
},
{
"id": "4e923cdb-6127-4068-80cf-6b5cd4ebc98c",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $('Look for incoming emails').item.json.Subject }}",
"rightValue": "boking"
},
{
"id": "f6b9bf16-ad15-4e02-a78e-7e0b6a3adaf0",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $('Look for incoming emails').item.json.Subject }}",
"rightValue": "reserv"
},
{
"id": "77152b7b-6e55-494f-9e12-3e99c77570a6",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $('Look for incoming emails').item.json.Subject }}",
"rightValue": "booking"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "82f52b5b-1212-4519-89c2-f4d0556b9d9d",
"name": "Check for Attachment",
"type": "n8n-nodes-base.if",
"position": [
-2352,
1248
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "check-attachment",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ Object.keys($binary || {}).length }}",
"rightValue": 0
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "c70932ce-9208-4f45-9f07-a8f412d9ae93",
"name": "Extract Attachment Data",
"type": "n8n-nodes-base.extractFromFile",
"position": [
-2064,
1136
],
"parameters": {
"options": {},
"operation": "pdf",
"binaryPropertyName": "={{ Object.keys($json.binary || {})[0] || 'attachment_0' }}"
},
"typeVersion": 1
},
{
"id": "c9390ca4-1f47-45e2-a5af-e3c7598969eb",
"name": "Configuration: User Settings",
"type": "n8n-nodes-base.set",
"position": [
-1888,
272
],
"parameters": {
"values": {
"string": [
{
"name": "gSheetID",
"value": "ENTER_GOOGLE_SHEET_ID_HERE"
},
{
"name": "adminEmailForErrors",
"value": "admin@example.com"
}
]
},
"options": {}
},
"typeVersion": 1
},
{
"id": "e8ed62e0-3f84-4545-b202-4f45959928fe",
"name": "Get many messages (1)",
"type": "n8n-nodes-base.gmail",
"position": [
-2400,
640
],
"parameters": {
"limit": 1,
"simple": false,
"filters": {},
"options": {
"downloadAttachments": true
},
"operation": "getAll"
},
"typeVersion": 2.1
},
{
"id": "3d66a43d-b16c-4500-b655-b2dbf6c0a841",
"name": "Look for incoming emails",
"type": "n8n-nodes-base.gmailTrigger",
"position": [
-2624,
640
],
"parameters": {
"filters": {
"labelIds": [
"INBOX"
]
},
"pollTimes": {
"item": [
{
"mode": "everyHour"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "708e6de8-8325-48e8-a89c-6bde44a06206",
"name": "Validate Extraction",
"type": "n8n-nodes-base.code",
"position": [
-1328,
1264
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Check if AI extraction succeeded\nif ($json.error) {\n\treturn {\n\t\tjson: {\n\t\t\terror_occurred: true,\n\t\t\terror_type: \"AI_EXTRACTION_FAILED\",\n\t\t\terror_message: $json.error.message || \"AI extraction failed\",\n\t\t\toriginal_email: $('Get many messages (1)').item.json,\n\t\t\ttimestamp: new Date().toISOString()\n\t\t}\n\t};\n}\n\nif (!$json.output) {\n\treturn {\n\t\tjson: {\n\t\t\terror_occurred: true,\n\t\t\terror_type: \"NO_OUTPUT\",\n\t\t\terror_message: \"AI did not return any output\",\n\t\t\toriginal_email: $('Get many messages (1)').item.json,\n\t\t\ttimestamp: new Date().toISOString()\n\t\t}\n\t};\n}\n\ntry {\n\tlet bookingData;\n\t\n\tif (typeof $json.output === 'string') {\n\t\tbookingData = JSON.parse($json.output);\n\t} else {\n\t\tbookingData = $json.output;\n\t}\n\t\n\tconsole.log(\"Parsed booking data:\", JSON.stringify(bookingData, null, 2));\n\t\n\tconst requiredFields = ['travel_agency_name', 'number_of_rooms', 'check_in_date', 'check_out_date'];\n\tconst missingFields = [];\n\t\n\tfor (const field of requiredFields) {\n\t\tif (!bookingData[field] || bookingData[field] === null || bookingData[field] === \"null\" || bookingData[field] === \"\") {\n\t\t\tmissingFields.push(field);\n\t\t}\n\t}\n\t\n\tif (missingFields.length > 0) {\n\t\tconsole.log(\"Missing fields detected:\", missingFields);\n\t\treturn {\n\t\t\tjson: {\n\t\t\t\terror_occurred: true,\n\t\t\t\terror_type: \"MISSING_REQUIRED_FIELDS\",\n\t\t\t\terror_message: `Missing required fields: ${missingFields.join(', ')}`,\n\t\t\t\textracted_data: bookingData,\n\t\t\t\toriginal_email: $('Get many messages (1)').item.json,\n\t\t\t\ttimestamp: new Date().toISOString()\n\t\t\t}\n\t\t};\n\t}\n\t\n\tconst checkInDate = new Date(bookingData.check_in_date);\n\tconst checkOutDate = new Date(bookingData.check_out_date);\n\t\n\tif (isNaN(checkInDate.getTime()) || isNaN(checkOutDate.getTime())) {\n\t\tconsole.log(\"Invalid date format detected\");\n\t\treturn {\n\t\t\tjson: {\n\t\t\t\terror_occurred: true,\n\t\t\t\terror_type: \"INVALID_DATE_FORMAT\",\n\t\t\t\terror_message: \"Check-in or check-out date is invalid\",\n\t\t\t\textracted_data: bookingData,\n\t\t\t\toriginal_email: $('Get many messages (1)').item.json,\n\t\t\t\ttimestamp: new Date().toISOString()\n\t\t\t}\n\t\t};\n\t}\n\t\n\tif (checkOutDate <= checkInDate) {\n\t\tconsole.log(\"Date logic error detected\");\n\t\treturn {\n\t\t\tjson: {\n\t\t\t\terror_occurred: true,\n\t\t\t\terror_type: \"INVALID_DATE_LOGIC\",\n\t\t\t\terror_message: \"Check-out date must be after check-in date\",\n\t\t\t\textracted_data: bookingData,\n\t\t\t\toriginal_email: $('Get many messages (1)').item.json,\n\t\t\t\ttimestamp: new Date().toISOString()\n\t\t\t}\n\t\t};\n\t}\n\t\n\tconsole.log(\"Validation successful!\");\n\treturn {\n\t\tjson: {\n\t\t\terror_occurred: false,\n\t\t\tvalidated_data: bookingData\n\t\t}\n\t};\n\t\n} catch (error) {\n\tconsole.log(\"JSON parse error:\", error.message);\n\treturn {\n\t\tjson: {\n\t\t\terror_occurred: true,\n\t\t\terror_type: \"JSON_PARSE_ERROR\",\n\t\t\terror_message: error.message,\n\t\t\traw_output: $json.output,\n\t\t\toriginal_email: $('Get many messages (1)').item.json,\n\t\t\ttimestamp: new Date().toISOString()\n\t\t}\n\t};\n}"
},
"typeVersion": 2
},
{
"id": "928fd9b5-f351-4f2f-a67d-e8ac424806c6",
"name": "Apply Business Rules",
"type": "n8n-nodes-base.code",
"position": [
-816,
944
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const bookingData = $json.validated_data;\n\nlet assignedTeam = \"\";\nlet teamEmail = \"\";\nlet priority = \"Normal\";\nlet status = \"New\";\n\nconst numRooms = parseInt(bookingData.number_of_rooms) || 0;\n\n// Rule 1: Assignment based on number of rooms\nif (numRooms >= 50) {\n\tassignedTeam = \"Large Bookings Team\";\n\tteamEmail = \"user@example.com\";\n\tpriority = \"High\";\n} else if (numRooms >= 20) {\n\tassignedTeam = \"Group Bookings Team\";\n\tteamEmail = \"user@example.com\";\n\tpriority = \"Medium\";\n} else {\n\tassignedTeam = \"Regular Bookings Team\";\n\tteamEmail = \"user@example.com\";\n\tpriority = \"Normal\";\n}\n\n// Rule 2: Urgency override\nif (bookingData.urgency === \"urgent\") {\n\tpriority = \"High\";\n\tassignedTeam = \"Urgent Bookings Team\";\n\tteamEmail = \"user@example.com\";\n}\n\n// Rule 3: Check-in date proximity\nconst checkInDate = new Date(bookingData.check_in_date);\nconst today = new Date();\nconst daysUntilCheckIn = Math.ceil((checkInDate - today) / (1000 * 60 * 60 * 24));\n\nif (daysUntilCheckIn <= 7 && daysUntilCheckIn >= 0) {\n\tpriority = \"High\";\n\tstatus = \"Urgent - Near Check-in\";\n}\n\n// Rule 4: VIP Agencies\nconst vipAgencies = [\"Premium Travel Co\", \"Elite Bookings\", \"Luxury Tours\"];\nif (vipAgencies.some(agency => bookingData.travel_agency_name?.includes(agency))) {\n\tpriority = \"High\";\n\tassignedTeam = \"VIP Relations Team\";\n\tteamEmail = \"user@example.com\";\n}\n\n// Calculate total nights\nconst checkOutDate = new Date(bookingData.check_out_date);\nconst totalNights = Math.ceil((checkOutDate - checkInDate) / (1000 * 60 * 60 * 24));\n\n// Get original email info - FIXED LINE BELOW\nconst originalEmail = $('Look for incoming emails').item.json;\n\nreturn {\n\tjson: {\n\t\t// Extracted booking data\n\t\ttravel_agency_name: bookingData.travel_agency_name || \"N/A\",\n\t\tcontact_person: bookingData.contact_person || \"N/A\",\n\t\tcontact_email: bookingData.contact_email || \"N/A\",\n\t\tcontact_phone: bookingData.contact_phone || \"N/A\",\n\t\tnumber_of_rooms: numRooms,\n\t\tcheck_in_date: bookingData.check_in_date || \"N/A\",\n\t\tcheck_out_date: bookingData.check_out_date || \"N/A\",\n\t\ttotal_nights: totalNights || 0,\n\t\troom_type: bookingData.room_type || \"Standard\",\n\t\tspecial_requests: bookingData.special_requests || \"None\",\n\t\ttotal_guests: bookingData.total_guests || numRooms * 2,\n\t\tbooking_reference: bookingData.booking_reference || `BK${Date.now()}`,\n\t\turgency: bookingData.urgency || \"normal\",\n\t\t\n\t\t// Business rules output\n\t\tassigned_team: assignedTeam,\n\t\tteam_email: teamEmail,\n\t\tpriority: priority,\n\t\tstatus: status,\n\t\tdays_until_checkin: daysUntilCheckIn,\n\t\t\n\t\t// Metadata\n\t\tprocessed_date: new Date().toISOString(),\n\t\toriginal_subject: originalEmail.subject,\n\t\toriginal_sender: originalEmail.from,\n\t\tcase_id: `CASE-${Date.now()}`,\n\t\tprocessing_status: \"SUCCESS\"\n\t}\n};"
},
"typeVersion": 2,
"continueOnFail": true
},
{
"id": "0c8b2e33-a8fb-47eb-aff7-afcb7d37b006",
"name": "Log Error to Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
-768,
1520
],
"parameters": {
"columns": {
"value": {
"timestamp": "={{ $json.timestamp }}",
"Error Type": "={{ $json.error_type }}",
"error_type": "={{ $json.error_type }}",
"email_snippet": "={{ $('Look for incoming emails').item.json.snippet }}",
"error_message": "={{ $json.error_message }}",
"extracted_data": "={{ $json.extracted_data }}",
"original_sender": "={{ $('Look for incoming emails').item.json.From }}",
"original_subject": "={{ $('Look for incoming emails').item.json.Subject }}",
"workflow_execution_id": "={{ $workflow.id }}"
},
"schema": [
{
"id": "timestamp",
"type": "string",
"displayName": "timestamp"
},
{
"id": "error_type",
"type": "string",
"displayName": "error_type"
},
{
"id": "error_message",
"type": "string",
"displayName": "error_message"
},
{
"id": "original_subject",
"type": "string",
"displayName": "original_subject"
},
{
"id": "original_sender",
"type": "string",
"displayName": "original_sender"
},
{
"id": "email_snippet",
"type": "string",
"displayName": "email_snippet"
},
{
"id": "extracted_data",
"type": "string",
"displayName": "extracted_data"
},
{
"id": "workflow_execution_id",
"type": "string",
"displayName": "workflow_execution_id"
},
{
"id": "Error Type",
"type": "string",
"displayName": "Error Type"
},
{
"id": "Meaning",
"type": "string",
"displayName": "Meaning"
},
{
"id": "Action Needed",
"type": "string",
"displayName": "Action Needed"
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "Error Logs",
"cachedResultName": "Error Logs"
},
"documentId": {
"__rl": true,
"mode": "expression",
"value": "={{ $('Configuration: User Settings').item.json.gSheetID }}"
}
},
"retryOnFail": true,
"typeVersion": 4.5,
"waitBetweenTries": 2000
},
{
"id": "3dca72e3-0a38-433a-b1e5-e1ef571c6ba0",
"name": "Send Error Notification",
"type": "n8n-nodes-base.gmail",
"position": [
-432,
1520
],
"parameters": {
"sendTo": "={{ $('Configuration: User Settings').item.json.adminEmailForErrors }}",
"message": "=<html>\n<body style=\"font-family: Arial, sans-serif; line-height: 1.6; color: #333;\">\n\t<div style=\"max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #dc3545; border-radius: 5px;\">\n\t\t<h2 style=\"color: #dc3545; border-bottom: 2px solid #dc3545; padding-bottom: 10px;\">\u26a0\ufe0f Booking Processing Error</h2>\n\t\t\n\t\t<div style=\"background-color: #f8d7da; padding: 15px; border-radius: 5px; margin: 20px 0; border-left: 4px solid #dc3545;\">\n\t\t\t<h3 style=\"color: #721c24; margin-top: 0;\">Error Details</h3>\n\t\t\t<p><strong>Error Type:</strong> {{ $json.error_type }}</p>\n\t\t\t<p><strong>Error Message:</strong> {{ $json.error_message }}</p>\n\t\t\t<p><strong>Timestamp:</strong> {{ $json.timestamp }}</p>\n\t\t</div>\n\t\t\n\t\t<div style=\"background-color: #fff3cd; padding: 15px; border-radius: 5px; margin: 20px 0; border-left: 4px solid #ffc107;\">\n\t\t\t<h3 style=\"color: #856404; margin-top: 0;\">Original Email Information</h3>\n\t\t\t<p><strong>From:</strong> {{ $('Get many messages (1)').item.json.from.value[0].address|| 'N/A' }}</p>\n\t\t\t<p><strong>Subject:</strong> {{ $('Get many messages (1)').item.json.subject|| 'N/A' }}</p>\n\t\t\t<p><strong>Date:</strong> {{ $('Get many messages (1)').item.json.headers.date || 'N/A' }}</p>\n\t\t\t<p><strong>Snippet:</strong> {{ $('Look for incoming emails').item.json.snippet|| 'N/A' }}</p>\n\t\t</div>\n\t\t\n\t\t<div style=\"background-color: #d1ecf1; padding: 15px; border-radius: 5px; margin: 20px 0; border-left: 4px solid #17a2b8;\">\n\t\t\t<h3 style=\"color: #0c5460; margin-top: 0;\">Extracted Data (if any)</h3>\n\t\t\t<pre style=\"background-color: white; padding: 10px; border-radius: 3px; overflow-x: auto;\">{{ JSON.stringify($json.extracted_data, null, 2) }}</pre>\n\t\t</div>\n\t\t\n\t\t<div style=\"margin-top: 20px; padding: 15px; background-color: #e7f3ff; border-radius: 5px;\">\n\t\t\t<h3 style=\"color: #004085; margin-top: 0;\">Action Required</h3>\n\t\t\t<p>Please review this email manually and process the booking request.</p>\n\t\t\t<p><strong>Workflow Execution ID:</strong> {{ $execution.id }}</p>\n\t\t</div>\n\t\t\n\t\t<p style=\"margin-top: 30px; font-size: 12px; color: #666;\">This is an automated alert from the Hotel Booking Workflow System.</p>\n\t</div>\n</body>\n</html>",
"options": {
"appendAttribution": false
},
"subject": "=\u26a0\ufe0f Booking Processing Error - {{ $json.error_type }}"
},
"typeVersion": 2.1
},
{
"id": "8615af32-9474-4add-9510-43a5ee171eb8",
"name": "Guardrails1",
"type": "@n8n/n8n-nodes-langchain.guardrails",
"position": [
-2192,
640
],
"parameters": {
"text": "={{ $('Get many messages (1)').item.json.text }}",
"guardrails": {
"nsfw": {
"value": {
"threshold": 0.7
}
},
"topicalAlignment": {
"value": {
"prompt": "=You are a hotel booking assistant. Your job is to process booking requests.\n\nBUSINESS SCOPE:\n- The text SHOULD contain booking details, dates, guest counts, and room preferences.\n- The text MAY contain payment details, credit card numbers, VIP requests, or urgent markers.\n- The text is considered ON-TOPIC if it relates to hospitality, reservations, or travel.\n\nINSTRUCTIONS:\n- Only flag the content as off-topic if it is completely unrelated to hotel bookings (e.g., spam, marketing for other products, personal conversations about non-travel topics).\n- Do not flag credit card numbers or urgent requests as off-topic.",
"threshold": 0.7
}
}
}
},
"typeVersion": 1
},
{
"id": "280d81bc-6abe-4260-87f7-79cd71188737",
"name": "Log Team Assignment",
"type": "n8n-nodes-base.googleSheets",
"position": [
-48,
944
],
"parameters": {
"columns": {
"value": {
"case_id": "={{ $json.case_id }}",
"priority": "={{ $json.priority }}",
"timestamp": "={{ $json.processed_date }}",
"team_email": "={{ $json.team_email }}",
"assigned_team": "={{ $json.assigned_team }}",
"check_in_date": "={{ $json.check_in_date }}",
"travel_agency": "={{ $json.travel_agency_name }}",
"number_of_rooms": "={{ $json.number_of_rooms }}"
},
"schema": [
{
"id": "timestamp",
"type": "string",
"displayName": "timestamp"
},
{
"id": "case_id",
"type": "string",
"displayName": "case_id"
},
{
"id": "assigned_team",
"type": "string",
"displayName": "assigned_team"
},
{
"id": "team_email",
"type": "string",
"displayName": "team_email"
},
{
"id": "priority",
"type": "string",
"displayName": "priority"
},
{
"id": "number_of_rooms",
"type": "string",
"displayName": "number_of_rooms"
},
{
"id": "travel_agency",
"type": "string",
"displayName": "travel_agency"
},
{
"id": "check_in_date",
"type": "string",
"displayName": "check_in_date"
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "Team Assignments",
"cachedResultName": "Team Assignments"
},
"documentId": {
"__rl": true,
"mode": "expression",
"value": "={{ $('Configuration: User Settings').item.json.gSheetID }}"
}
},
"retryOnFail": true,
"typeVersion": 4.5,
"continueOnFail": true,
"waitBetweenTries": 2000
},
{
"id": "f276158f-b898-426e-8f63-e217b2294fc8",
"name": "Append to Cases Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
-48,
720
],
"parameters": {
"columns": {
"value": {
"status": "={{ $('Apply Business Rules').item.json.status }}",
"case_id": "={{ $('Apply Business Rules').item.json.case_id }}",
"urgency": "={{ $('Apply Business Rules').item.json.urgency }}",
"priority": "={{ $('Apply Business Rules').item.json.priority }}",
"room_type": "={{ $('Apply Business Rules').item.json.room_type }}",
"team_email": "={{ $('Apply Business Rules').item.json.team_email }}",
"total_guests": "={{ $('Apply Business Rules').item.json.total_guests }}",
"total_nights": "={{ $('Apply Business Rules').item.json.total_nights }}",
"assigned_team": "={{ $('Apply Business Rules').item.json.assigned_team }}",
"check_in_date": "={{ $('Apply Business Rules').item.json.check_in_date }}",
"contact_email": "={{ $('Apply Business Rules').item.json.contact_email }}",
"contact_phone": "={{ $('Apply Business Rules').item.json.contact_phone }}",
"check_out_date": "={{ $('Apply Business Rules').item.json.check_out_date }}",
"contact_person": "={{ $('Apply Business Rules').item.json.contact_person }}",
"processed_date": "={{ $('Apply Business Rules').item.json.processed_date }}",
"number_of_rooms": "={{ $('Apply Business Rules').item.json.number_of_rooms }}",
"original_sender": "={{ $('Get many messages (1)').item.json.from.value[0].address }}",
"original_subject": "={{ $('Look for incoming emails').item.json.Subject }}",
"special_requests": "={{ $('Apply Business Rules').item.json.special_requests }}",
"booking_reference": "={{ $('Apply Business Rules').item.json.booking_reference }}",
"days_until_checkin": "={{ $('Apply Business Rules').item.json.days_until_checkin }}",
"travel_agency_name": "={{ $('Apply Business Rules').item.json.travel_agency_name }}"
},
"schema": [
{
"id": "case_id",
"type": "string",
"displayName": "case_id"
},
{
"id": "processed_date",
"type": "string",
"displayName": "processed_date"
},
{
"id": "travel_agency_name",
"type": "string",
"displayName": "travel_agency_name"
},
{
"id": "contact_person",
"type": "string",
"displayName": "contact_person"
},
{
"id": "contact_email",
"type": "string",
"displayName": "contact_email"
},
{
"id": "contact_phone",
"type": "string",
"displayName": "contact_phone"
},
{
"id": "number_of_rooms",
"type": "string",
"displayName": "number_of_rooms"
},
{
"id": "check_in_date",
"type": "string",
"displayName": "check_in_date"
},
{
"id": "check_out_date",
"type": "string",
"displayName": "check_out_date"
},
{
"id": "total_nights",
"type": "string",
"displayName": "total_nights"
},
{
"id": "room_type",
"type": "string",
"displayName": "room_type"
},
{
"id": "total_guests",
"type": "string",
"displayName": "total_guests"
},
{
"id": "special_requests",
"type": "string",
"displayName": "special_requests"
},
{
"id": "booking_reference",
"type": "string",
"displayName": "booking_reference"
},
{
"id": "urgency",
"type": "string",
"displayName": "urgency"
},
{
"id": "assigned_team",
"type": "string",
"displayName": "assigned_team"
},
{
"id": "team_email",
"type": "string",
"displayName": "team_email"
},
{
"id": "priority",
"type": "string",
"displayName": "priority"
},
{
"id": "status",
"type": "string",
"displayName": "status"
},
{
"id": "days_until_checkin",
"type": "string",
"displayName": "days_until_checkin"
},
{
"id": "original_sender",
"type": "string",
"displayName": "original_sender"
},
{
"id": "original_subject",
"type": "string",
"displayName": "original_subject"
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "Cases",
"cachedResultName": "Cases"
},
"documentId": {
"__rl": true,
"mode": "expression",
"value": "={{ $('Configuration: User Settings').item.json.gSheetID }}"
}
},
"retryOnFail": true,
"typeVersion": 4.5,
"continueOnFail": true,
"waitBetweenTries": 2000
},
{
"id": "2499ed0d-a0cc-4ac5-9712-a075fab7edf6",
"name": "Send Confirmation Email",
"type": "n8n-nodes-base.gmail",
"position": [
208,
720
],
"parameters": {
"sendTo": "={{ $json.contact_email }}",
"message": "=<html>\n<body style=\"font-family: Arial, sans-serif; line-height: 1.6; color: #333;\">\n\t<div style=\"max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #ddd;\">\n\t\t<h2 style=\"color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px;\">Booking Request Confirmation</h2>\n\t\t\n\t\t<p>Dear {{ $json.contact_person }},</p>\n\t\t\n\t\t<p>Thank yourself for your booking request. Your reservation has been received and is being processed by our <strong>{{ $json.assigned_team }}</strong>.</p>\n\t\t\n\t\t<div style=\"background-color: #f8f9fa; padding: 15px; border-radius: 5px; margin: 20px 0;\">\n\t\t\t<h3 style=\"color: #2c3e50; margin-top: 0;\">Booking Details</h3>\n\t\t\t<table style=\"width: 100%; border-collapse: collapse;\">\n\t\t\t\t<tr>\n\t\t\t\t\t<td style=\"padding: 5px 0;\"><strong>Case Reference:</strong></td>\n\t\t\t\t\t<td style=\"padding: 5px 0;\">{{ $json.case_id }}</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td style=\"padding: 5px 0;\"><strong>Priority:</strong></td>\n\t\t\t\t\t<td style=\"padding: 5px 0;\"><span style=\"background-color: #{{ $json.priority === 'High' ? 'e74c3c' : $json.priority === 'Medium' ? 'f39c12' : '27ae60' }}; color: white; padding: 2px 8px; border-radius: 3px;\">{{ $json.priority }}</span></td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td style=\"padding: 5px 0;\"><strong>Number of Rooms:</strong></td>\n\t\t\t\t\t<td style=\"padding: 5px 0;\">{{ $json.number_of_rooms }}</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td style=\"padding: 5px 0;\"><strong>Check-in Date:</strong></td>\n\t\t\t\t\t<td style=\"padding: 5px 0;\">{{ $json.check_in_date }}</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td style=\"padding: 5px 0;\"><strong>Check-out Date:</strong></td>\n\t\t\t\t\t<td style=\"padding: 5px 0;\">{{ $json.check_out_date }}</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td style=\"padding: 5px 0;\"><strong>Total Nights:</strong></td>\n\t\t\t\t\t<td style=\"padding: 5px 0;\">{{ $json.total_nights }}</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td style=\"padding: 5px 0;\"><strong>Total Guests:</strong></td>\n\t\t\t\t\t<td style=\"padding: 5px 0;\">{{ $json.total_guests }}</td>\n\t\t\t\t</tr>\n\t\t\t</table>\n\t\t</div>\n\t\t\n\t\t<p><strong>Status:</strong> {{ $json.status }}</p>\n\t\t<p><strong>Booking Reference:</strong> {{ $json.booking_reference }}</p>\n\t\t\n\t\t<p style=\"margin-top: 20px;\">Our team will review your request and respond within <strong>24 hours</strong>.</p>\n\t\t\n\t\t<p>If you have any questions, please contact us at {{ $json.team_email }}</p>\n\t\t\n\t\t<p style=\"margin-top: 30px;\">Best regards,<br>\n\t\t<strong>Hotel Booking Team</strong></p>\n\t</div>\n</body>\n</html>",
"options": {
"appendAttribution": false
},
"subject": "=Booking Request Received - Ref #{{ $json.case_id }}"
},
"typeVersion": 2.1,
"continueOnFail": true
},
{
"id": "da69b08e-6254-47b4-8286-a656b98a9156",
"name": "Log Success Metrics",
"type": "n8n-nodes-base.googleSheets",
"position": [
576,
720
],
"parameters": {
"columns": {
"value": {
"case_id": "={{ $('Apply Business Rules').item.json.case_id }}",
"priority": "={{ $('Apply Business Rules').item.json.priority }}",
"timestamp": "={{ $today }}",
"email_sent": "={{ $('Send Confirmation Email').item.json.error ? 'No' : 'Yes' }}",
"assigned_team": "={{ $('Apply Business Rules').item.json.assigned_team }}",
"sheets_updated": "Yes",
"number_of_rooms": "={{ $('Apply Business Rules').item.json.number_of_rooms }}",
"processing_time_seconds": "={{ Math.round(($execution.startedAt ? (Date.now() - new Date($execution.startedAt).getTime()) / 1000 : 0)) }}"
},
"schema": [
{
"id": "timestamp",
"type": "string",
"displayName": "timestamp"
},
{
"id": "case_id",
"type": "string",
"displayName": "case_id"
},
{
"id": "processing_time_seconds",
"type": "string",
"displayName": "processing_time_seconds"
},
{
"id": "assigned_team",
"type": "string",
"displayName": "assigned_team"
},
{
"id": "priority",
"type": "string",
"displayName": "priority"
},
{
"id": "number_of_rooms",
"type": "string",
"displayName": "number_of_rooms"
},
{
"id": "email_sent",
"type": "string",
"displayName": "email_sent"
},
{
"id": "sheets_updated",
"type": "string",
"displayName": "sheets_updated"
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "Success Metrics",
"cachedResultName": "Success Metrics"
},
"documentId": {
"__rl": true,
"mode": "expression",
"value": "={{ $('Configuration: User Settings').item.json.gSheetID }}"
}
},
"retryOnFail": true,
"typeVersion": 4.5,
"waitBetweenTries": 2000
},
{
"id": "58dbbd37-8a9d-4007-912c-92e936048948",
"name": "Check for Errors",
"type": "n8n-nodes-base.if",
"position": [
-1056,
1264
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "check-error",
"operator": {
"type": "boolean",
"operation": "false"
},
"leftValue": "={{ $json.error_occurred }}",
"rightValue": ""
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "389d5e49-5370-4b72-aed5-b35cc4d597c2",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-192,
1408
],
"parameters": {
"color": 2,
"width": 528,
"height": 976,
"content": "## Error Notification\n\n"
},
"typeVersion": 1
},
{
"id": "afb55352-1c01-4a70-99ad-50b16773df64",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
0
],
"parameters": {
"color": 3,
"width": 464,
"height": 592,
"content": "## Confirmation Email\n\n"
},
"typeVersion": 1
},
{
"id": "aad71e5a-b332-4c43-b4cf-7eee83f6d3e0",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1968,
128
],
"parameters": {
"color": 7,
"width": 256,
"height": 304,
"content": "### 3. Workflow configuration\n- Add your **google sheet ID*\n- Add the **email address** you want error notifications sent to."
},
"typeVersion": 1
},
{
"id": "424a7235-2496-4610-a0fe-4588cfd0cdcd",
"name": "Sticky Note24",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1360,
1040
],
"parameters": {
"color": 7,
"width": 432,
"height": 400,
"content": "## Validate the data and check for missing values\nChecks for:\n\u2713 Required fields present (agency, rooms, dates)\n\u2713 Valid date format (YYYY-MM-DD)\n\u2713 Check-out after check-in\n\u2713 No null/empty critical values\nErrors are caught and routed to error handler. "
},
"typeVersion": 1
},
{
"id": "c0e9ffe0-0933-4d83-8888-0b950b3eea6e",
"name": "Sticky Note21",
"type": "n8n-nodes-base.stickyNote",
"position": [
-864,
1408
],
"parameters": {
"color": 2,
"width": 640,
"height": 304,
"content": "## \u26a0\ufe0f Error Handling Flow\n### Any failures are logged and admins are notified"
},
"typeVersion": 1
},
{
"id": "5f4a9ac7-43a5-4939-8d84-83c816a711eb",
"name": "Sticky Note20",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2160,
912
],
"parameters": {
"color": 4,
"width": 736,
"height": 736,
"content": "## Extract *data from PDF*\n### The Agent extracts the data from the attachment in mail and processes them. \nUses OpenAI GPT-4o-mini with JSON mode to extract:\n- Travel agency name, contact details\n- Room count, dates, guest count\n- Special requests, urgency level\nTemperature set to 0.3 for consistent extraction\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n## Extract *email body* \n"
},
"typeVersion": 1
},
{
"id": "cd241238-65ff-4937-9380-a9c22abfab56",
"name": "Sticky Note19",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2688,
448
],
"parameters": {
"color": 5,
"width": 1568,
"height": 432,
"content": "## Mail extraction\n### Extract the mails thats have to do with booking or reservations.\n Filters emails by checking subject line for keywords:\n- \"Booking Request\", \"reservation\", \"room request\", \"accommodation\", \"check-in\", \"booking\"\nUses OR logic - any match proceeds to next step"
},
"typeVersion": 1
},
{
"id": "6e1a215d-1e11-4666-9822-d544f2cc4051",
"name": "Sticky Note18",
"type": "n8n-nodes-base.stickyNote",
"position": [
-864,
608
],
"parameters": {
"color": 3,
"width": 1248,
"height": 560,
"content": "## Log to *Case* and *Team Assignment* tables\n**- Make a copy of this sheet:**\nhttps://docs.google.com/spreadsheets/d/1qhUoE4baN5TyO51mD2caYU789XizhXl3UDCCT0v83So/copy"
},
"typeVersion": 1
},
{
"id": "89d8dd05-2bf0-4091-8931-38faeb6c81d6",
"name": "Sticky Note17",
"type": "n8n-nodes-base.stickyNote",
"position": [
464,
656
],
"parameters": {
"color": 6,
"width": 352,
"height": 248,
"content": "## \ud83d\udcca Success Metrics Logger"
},
"typeVersion": 1
},
{
"id": "a8d4be9c-ab0d-4cfc-be2b-95936ba88017",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3216,
416
],
"parameters": {
"width": 496,
"height": 1168,
"content": "# \ud83c\udfe8 Automate hotel booking requests from Gmail to Google Sheets using AI\n### **[Gtaras](https://n8n.io/creators/tarasidis/)** \n\nThis workflow automates *hotel booking requests that come in through Gmail by instantly extracting data, intelligently routing it to a team, and tracking performance reliably*. it can be useful for hotels, travel agencies, or operations teams that want to avoid data entry. \n\n## How it Works (Core Flow)\n\nThe workflow runs hourly and scans your Gmail inbox for booking-related emails. It then utilizes the **OpenAI (GPT-4o-mini)** to extract key details from either the email body or attached PDFs and validates the fields and dates. Then comes the application of business rules for priority and team allocation based on room count or urgency.\n\n## Benefits of Automating\n\nThe process logs all successful bookings and assignments to Google Sheets across Cases, Team Assignments, and Success Metrics tabs. In the event of any failed extraction, strong error handling ensures the request is logged in the Error Log tab and an administrator is notified, hence ensuring that no request is ever lost.\n\n## \u2699\ufe0f Setup Requirements\n**3 Steps Credentials:** \n1) Add your Gmail, Google Sheets, and OpenAI (GPT-4o-mini) account credentials to n8n. \n\n2) Google Sheet: Duplicate the following template (link is available in the workflow description) within your Google Drive account. \n\n3) Configuration: **Update** the central \"Configuration: User Settings\" node with your copied Google Sheet ID and dedicated admin email address where error alerts should be sent.\n\n## \ud83d\udccb Quick Setup Checklist\n1. \u2705 Connect Gmail OAuth2 (Settings \u2192 Credentials)\n2. \u2705 Connect Google Sheets OAuth2\n3. \u2705 Add OpenAI API key\n4. \u2705 Create Google Sheet with 4 tabs: Cases, Team Assignments, Error Logs, Success Metrics\n5. \u2705 Update Configuration node with your settings\n6. \u2705 Test with a sample booking email\n7. \u2705 Adjust trigger frequency (default: hourly)"
},
"typeVersion": 1
},
{
"id": "40f8bf83-bed7-4259-a216-ce6840750317",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1632,
128
],
"parameters": {
"color": 2,
"width": 800,
"height": 304,
"content": "## \u26a0\ufe0f Error Handling Flow\n### Any failures are logged and admins are notified"
},
"typeVersion": 1
},
{
"id": "5edbeb68-2d3e-49ab-ba38-03e3f3df80fa",
"name": "GEMINI 2.5 FLASH",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
-2192,
736
],
"parameters": {
"options": {}
},
"typeVersion": 1
},
{
"id": "01fe2d7a-0a01-4a65-99f4-57d6a4c301f2",
"name": "CHAT GPT 5 mini",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
-1760,
1296
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-5-mini",
"cachedResultName": "gpt-5-mini"
},
"options": {
"temperature": 0.3,
"responseFormat": "json_object"
}
},
"typeVersion": 1.2
}
],
"connections": {
"Guardrails": {
"main": [
[
{
"node": "Merge Sanitized Data",
"type": "main",
"index": 0
}
]
]
},
"Guardrails1": {
"main": [
[
{
"node": "Configuration: User Settings",
"type": "main",
"index": 0
}
]
]
},
"CHAT GPT 5 mini": {
"ai_languageModel": [
[
{
"node": "OpenAI Model for PDF",
"type": "ai_languageModel",
"index": 0
},
{
"node": "OpenAI Model for Email Text2",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Check for Errors": {
"main": [
[
{
"node": "Apply Business Rules",
"type": "main",
"index": 0
}
],
[
{
"node": "Log Error to Sheet",
"type": "main",
"index": 0
}
]
]
},
"GEMINI 2.5 FLASH": {
"ai_languageModel": [
[
{
"node": "Guardrails1",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Log Error to Sheet": {
"main": [
[
{
"node": "Send Error Notification",
"type": "main",
"index": 0
}
]
]
},
"Validate Extraction": {
"main": [
[
{
"node": "Check for Errors",
"type": "main",
"index": 0
}
]
]
},
"Apply Business Rules": {
"main": [
[
{
"node": "Log Team Assignment",
"type": "main",
"index": 0
},
{
"node": "Guardrails",
"type": "main",
"index": 0
}
]
]
},
"Check for Attachment": {
"main": [
[
{
"node": "Extract Attachment Data",
"type": "main",
"index": 0
}
],
[
{
"node": "OpenAI Model for Email Text2",
"type": "main",
"index": 0
}
]
]
},
"IF (Guardrail Check)": {
"main": [
[
{
"node": "Filter Booking Emails",
"type": "main",
"index": 0
}
],
[
{
"node": "Set Invalid Email Error",
"type": "main",
"index": 0
}
]
]
},
"Merge Sanitized Data": {
"main": [
[
{
"node": "Append to Cases Sheet",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Model for PDF": {
"main": [
[
{
"node": "Validate Extraction",
"type": "main",
"index": 0
}
]
]
},
"Append to Cases Sheet": {
"main": [
[
{
"node": "Send Confirmation Email",
"type": "main",
"index": 0
}
]
]
},
"Filter Booking Emails": {
"main": [
[
{
"node": "Check for Attachment",
"type": "main",
"index": 0
}
]
]
},
"Get many messages (1)": {
"main": [
[
{
"node": "Guardrails1",
"type": "main",
"index": 0
}
]
]
},
"Extract Attachment Data": {
"main": [
[
{
"node": "OpenAI Model for PDF",
"type": "main",
"index": 0
}
]
]
},
"Log Invalid Email Error": {
"main": [
[
{
"node": "Send Invalid Email Notification",
"type": "main",
"index": 0
}
]
]
},
"Send Confirmation Email": {
"main": [
[
{
"node": "Log Success
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow is for hotel managers, travel agencies, and hospitality teams who receive booking requests via email. It eliminates the need for manual data entry by automatically parsing emails and attachments, assigning booking cases to the right teams, and tracking performance…
Source: https://n8n.io/workflows/10578/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Manual financial reconciliation is tedious and prone to error. This workflow functions as an AI Financial Controller, automatically monitoring your inbox for invoices, receipts, and bills, extracting
> Note: This workflow uses sticky notes extensively to document each logical section of the automation. Sticky notes are mandatory and already included to explain OCR, AI parsing, folder logic, dup
This template and YouTube video goes over 5 different implementations of evaluations within n8n. Categorization Correctness Tools used String similarity Helpfulness
Turn a simple email workflow into a LinkedIn content machine. Generate post ideas, draft full posts, and auto-publish to LinkedIn all controlled by replying to emails.
Enterprise-grade resume screening automation built for production environments. This workflow combines intelligent AI analysis with comprehensive error handling to ensure reliable processing of candid