
Smart Travel Package Finder - Search Flights & Hotels with Skyscanner-Booking.com
Description
Categories
📊 Productivity🤖 AI & Machine Learning
Nodes Used
n8n-nodes-base.setn8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.gmailn8n-nodes-base.mergen8n-nodes-base.webhookn8n-nodes-base.stickyNoten8n-nodes-base.stickyNoten8n-nodes-base.stickyNoten8n-nodes-base.httpRequest
PriceGratis
Views0
Last Updated11/28/2025
workflow.json
{
"id": "Q9TXNQXxpkuynljZ",
"meta": {
"instanceId": "b91e510ebae4127f953fd2f5f8d40d58ca1e71c746d4500c12ae86aad04c1502"
},
"name": "Smart Travel Package Finder - Search Flights & Hotels with Skyscanner-Booking.com",
"tags": [],
"nodes": [
{
"id": "e1964e90-e7d0-480d-8eed-36dd27d9d648",
"name": "📥 Travel Request Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
-640,
-384
],
"webhookId": "travel-itinerary-generator",
"parameters": {
"path": "travel-search",
"options": {
"rawBody": true
},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2
},
{
"id": "d8b0f0fc-3ab8-4f95-a9c8-6012ad56a9ab",
"name": "📝 Parse & Validate Inputs",
"type": "n8n-nodes-base.set",
"position": [
-480,
-384
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "destination",
"name": "destination",
"type": "string",
"value": "={{ $json.body.destination || 'Shanghai' }}"
},
{
"id": "departure",
"name": "departure",
"type": "string",
"value": "={{ $json.body.departure || 'New York' }}"
},
{
"id": "checkInDate",
"name": "checkInDate",
"type": "string",
"value": "={{ $json.body.checkInDate || '2025-12-01' }}"
},
{
"id": "checkOutDate",
"name": "checkOutDate",
"type": "string",
"value": "={{ $json.body.checkOutDate || '2025-12-08' }}"
},
{
"id": "notificationEmail",
"name": "notificationEmail",
"type": "string",
"value": "={{ $json.body.notificationEmail || $json.body.email }}"
},
{
"id": "adults",
"name": "adults",
"type": "number",
"value": "={{ $json.body.adults || 1 }}"
}
]
}
},
"typeVersion": 3.3
},
{
"id": "89686042-6637-485a-ae2a-88eb5a2a7680",
"name": "✈️ Search Flights (Skyscanner)",
"type": "n8n-nodes-base.httpRequest",
"position": [
-304,
-496
],
"parameters": {
"url": "https://sky-scrapper.p.rapidapi.com/api/v1/flights/searchFlights",
"options": {
"timeout": 30000
},
"sendQuery": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "originSkyId",
"value": "={{ $json.departure }}"
},
{
"name": "destinationSkyId",
"value": "={{ $json.destination }}"
},
{
"name": "originEntityId",
"value": "27537542"
},
{
"name": "destinationEntityId",
"value": "27537579"
},
{
"name": "date",
"value": "={{ $json.checkInDate }}"
},
{
"name": "adults",
"value": "={{ $json.adults }}"
},
{
"name": "currency",
"value": "USD"
},
{
"name": "market",
"value": "en-US"
},
{
"name": "countryCode",
"value": "US"
}
]
}
},
"typeVersion": 4.2,
"continueOnFail": true
},
{
"id": "f752c284-3bc5-430e-8193-da6876f52f66",
"name": "🏨 Search Hotels (Booking.com)",
"type": "n8n-nodes-base.httpRequest",
"position": [
-304,
-288
],
"parameters": {
"url": "https://booking-com.p.rapidapi.com/v1/hotels/search",
"options": {
"timeout": 30000
},
"sendQuery": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "dest_type",
"value": "city"
},
{
"name": "dest_id",
"value": "-1746443"
},
{
"name": "checkin_date",
"value": "={{ $json.checkInDate }}"
},
{
"name": "checkout_date",
"value": "={{ $json.checkOutDate }}"
},
{
"name": "adults_number",
"value": "={{ $json.adults }}"
},
{
"name": "order_by",
"value": "price"
},
{
"name": "filter_by_currency",
"value": "USD"
},
{
"name": "units",
"value": "metric"
},
{
"name": "room_number",
"value": "1"
},
{
"name": "page_number",
"value": "0"
}
]
}
},
"typeVersion": 4.2,
"continueOnFail": true
},
{
"id": "6f48d727-b21c-4b6f-b9b5-b8b4de4fee95",
"name": "🔀 Merge Flight & Hotel Data",
"type": "n8n-nodes-base.merge",
"position": [
-96,
-496
],
"parameters": {
"mode": "combine",
"options": {}
},
"typeVersion": 3
},
{
"id": "5267fce7-f040-4a6c-8159-23351371c325",
"name": "🧮 Generate Itinerary Combinations",
"type": "n8n-nodes-base.code",
"position": [
80,
-496
],
"parameters": {
"jsCode": "// Travel Itinerary Combination Engine\n// Combines flights and hotels into ranked packages\n\nconst inputData = $input.all();\n\n// Extract flight and hotel data from merged inputs\nlet flightData = [];\nlet hotelData = [];\nlet searchParams = {};\n\n// Parse the merged data\nfor (const item of inputData) {\n if (item.json.data && item.json.data.itineraries) {\n // Flight data\n const flights = item.json.data.itineraries.results || [];\n flightData = flights.slice(0, 10); // Top 10 flights\n } else if (item.json.result) {\n // Hotel data\n const hotels = item.json.result || [];\n hotelData = hotels.slice(0, 10); // Top 10 hotels\n }\n \n // Capture search parameters\n if (item.json.destination) {\n searchParams = {\n destination: item.json.destination,\n departure: item.json.departure,\n checkInDate: item.json.checkInDate,\n checkOutDate: item.json.checkOutDate,\n notificationEmail: item.json.notificationEmail,\n adults: item.json.adults || 1\n };\n }\n}\n\n// Fallback: Create mock data if APIs failed\nif (flightData.length === 0) {\n console.log('No flight data - using mock data');\n flightData = [\n {\n id: 'mock-flight-1',\n price: { raw: 650, formatted: '$650' },\n legs: [{\n origin: { displayCode: searchParams.departure || 'NYC' },\n destination: { displayCode: searchParams.destination || 'PVG' },\n departure: searchParams.checkInDate + 'T08:00:00',\n arrival: searchParams.checkInDate + 'T14:30:00',\n durationInMinutes: 870,\n carriers: { marketing: [{ name: 'United Airlines' }] }\n }]\n },\n {\n id: 'mock-flight-2',\n price: { raw: 720, formatted: '$720' },\n legs: [{\n origin: { displayCode: searchParams.departure || 'NYC' },\n destination: { displayCode: searchParams.destination || 'PVG' },\n departure: searchParams.checkInDate + 'T10:30:00',\n arrival: searchParams.checkInDate + 'T17:00:00',\n durationInMinutes: 870,\n carriers: { marketing: [{ name: 'Delta Airlines' }] }\n }]\n },\n {\n id: 'mock-flight-3',\n price: { raw: 580, formatted: '$580' },\n legs: [{\n origin: { displayCode: searchParams.departure || 'NYC' },\n destination: { displayCode: searchParams.destination || 'PVG' },\n departure: searchParams.checkInDate + 'T14:00:00',\n arrival: searchParams.checkInDate + 'T20:30:00',\n durationInMinutes: 870,\n carriers: { marketing: [{ name: 'American Airlines' }] }\n }]\n }\n ];\n}\n\nif (hotelData.length === 0) {\n console.log('No hotel data - using mock data');\n const nights = calculateNights(searchParams.checkInDate, searchParams.checkOutDate);\n hotelData = [\n {\n hotel_id: 'mock-hotel-1',\n hotel_name: 'Shanghai Grand Hotel',\n price_breakdown: { gross_price: 150 * nights },\n review_score: 8.5,\n address: 'Pudong District, Shanghai',\n url: 'https://booking.com/hotel-1',\n pricePerNight: 150,\n totalNights: nights\n },\n {\n hotel_id: 'mock-hotel-2',\n hotel_name: 'Bund Riverside Inn',\n price_breakdown: { gross_price: 120 * nights },\n review_score: 8.2,\n address: 'The Bund, Shanghai',\n url: 'https://booking.com/hotel-2',\n pricePerNight: 120,\n totalNights: nights\n },\n {\n hotel_id: 'mock-hotel-3',\n hotel_name: 'Lujiazui Business Hotel',\n price_breakdown: { gross_price: 180 * nights },\n review_score: 8.8,\n address: 'Lujiazui, Shanghai',\n url: 'https://booking.com/hotel-3',\n pricePerNight: 180,\n totalNights: nights\n }\n ];\n}\n\n// Calculate nights\nfunction calculateNights(checkIn, checkOut) {\n const start = new Date(checkIn);\n const end = new Date(checkOut);\n return Math.ceil((end - start) / (1000 * 60 * 60 * 24));\n}\n\nconst nights = calculateNights(searchParams.checkInDate, searchParams.checkOutDate);\n\n// Process and normalize flight data\nconst processedFlights = flightData.map(flight => {\n const leg = flight.legs ? flight.legs[0] : {};\n return {\n id: flight.id,\n airline: leg.carriers?.marketing?.[0]?.name || 'Unknown Airline',\n origin: leg.origin?.displayCode || searchParams.departure,\n destination: leg.destination?.displayCode || searchParams.destination,\n departure: leg.departure || searchParams.checkInDate,\n arrival: leg.arrival || searchParams.checkInDate,\n duration: leg.durationInMinutes ? `${Math.floor(leg.durationInMinutes / 60)}h ${leg.durationInMinutes % 60}m` : 'N/A',\n price: flight.price?.raw || 0,\n priceFormatted: flight.price?.formatted || `$${flight.price?.raw || 0}`,\n bookingLink: `https://www.skyscanner.com/transport/flights/${leg.origin?.displayCode}/${leg.destination?.displayCode}`\n };\n});\n\n// Process and normalize hotel data\nconst processedHotels = hotelData.map(hotel => {\n const totalPrice = hotel.price_breakdown?.gross_price || hotel.pricePerNight * nights || 0;\n return {\n id: hotel.hotel_id,\n name: hotel.hotel_name || 'Unknown Hotel',\n rating: hotel.review_score || 0,\n address: hotel.address || searchParams.destination,\n pricePerNight: hotel.pricePerNight || Math.round(totalPrice / nights),\n totalPrice: totalPrice,\n nights: nights,\n bookingLink: hotel.url || `https://www.booking.com/hotel/${hotel.hotel_id}.html`\n };\n});\n\n// Create all possible combinations\nconst itineraries = [];\nfor (const flight of processedFlights) {\n for (const hotel of processedHotels) {\n itineraries.push({\n id: `${flight.id}-${hotel.id}`,\n flight: flight,\n hotel: hotel,\n totalPrice: flight.price + hotel.totalPrice,\n savings: 0 // Will calculate after sorting\n });\n }\n}\n\n// Sort by total price (cheapest first)\nitineraries.sort((a, b) => a.totalPrice - b.totalPrice);\n\n// Calculate savings compared to most expensive option\nconst mostExpensive = itineraries[itineraries.length - 1].totalPrice;\nitineraries.forEach(item => {\n item.savings = mostExpensive - item.totalPrice;\n});\n\n// Get top 5 itineraries\nconst topItineraries = itineraries.slice(0, 5);\n\n// Return result with metadata\nreturn [{\n json: {\n searchParams: searchParams,\n itineraries: topItineraries,\n totalCombinations: itineraries.length,\n flightsFound: processedFlights.length,\n hotelsFound: processedHotels.length,\n generatedAt: new Date().toISOString()\n }\n}];"
},
"typeVersion": 2
},
{
"id": "80f6f70c-8df0-42db-ba0c-97def58e5a36",
"name": "🎨 Format HTML Email",
"type": "n8n-nodes-base.code",
"position": [
272,
-496
],
"parameters": {
"jsCode": "// HTML Email Generator for Travel Itineraries\n\nconst data = $input.first().json;\nconst { searchParams, itineraries, totalCombinations, flightsFound, hotelsFound } = data;\n\n// Format currency\nfunction formatCurrency(amount) {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD'\n }).format(amount);\n}\n\n// Format date\nfunction formatDate(dateString) {\n const date = new Date(dateString);\n return date.toLocaleDateString('en-US', {\n weekday: 'short',\n year: 'numeric',\n month: 'short',\n day: 'numeric'\n });\n}\n\n// Format time\nfunction formatTime(dateString) {\n const date = new Date(dateString);\n return date.toLocaleTimeString('en-US', {\n hour: '2-digit',\n minute: '2-digit'\n });\n}\n\n// Generate itinerary cards HTML\nfunction generateItineraryCards() {\n return itineraries.map((itinerary, index) => {\n const ranking = index + 1;\n const badge = ranking === 1 ? '<span style=\"background: #10b981; color: white; padding: 4px 12px; border-radius: 12px; font-size: 12px; font-weight: 600; margin-left: 10px;\">🏆 BEST VALUE</span>' : '';\n \n return `\n <div style=\"background: white; border-radius: 12px; padding: 24px; margin-bottom: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); border: ${ranking === 1 ? '3px solid #10b981' : '2px solid #e5e7eb'};\">\n <div style=\"display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;\">\n <h2 style=\"margin: 0; color: #1f2937; font-size: 20px;\">Option ${ranking}${badge}</h2>\n <div style=\"text-align: right;\">\n <div style=\"font-size: 28px; font-weight: bold; color: #10b981;\">${formatCurrency(itinerary.totalPrice)}</div>\n ${itinerary.savings > 0 ? `<div style=\"color: #6b7280; font-size: 14px;\">Save ${formatCurrency(itinerary.savings)}</div>` : ''}\n </div>\n </div>\n \n <!-- Flight Section -->\n <div style=\"background: #f9fafb; border-radius: 8px; padding: 16px; margin-bottom: 16px;\">\n <div style=\"display: flex; align-items: center; margin-bottom: 12px;\">\n <span style=\"font-size: 20px; margin-right: 8px;\">✈️</span>\n <h3 style=\"margin: 0; color: #374151; font-size: 16px;\">Flight Details</h3>\n </div>\n <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 12px;\">\n <div>\n <div style=\"color: #6b7280; font-size: 12px; margin-bottom: 4px;\">Airline</div>\n <div style=\"color: #1f2937; font-weight: 600;\">${itinerary.flight.airline}</div>\n </div>\n <div>\n <div style=\"color: #6b7280; font-size: 12px; margin-bottom: 4px;\">Duration</div>\n <div style=\"color: #1f2937; font-weight: 600;\">${itinerary.flight.duration}</div>\n </div>\n </div>\n <div style=\"display: flex; justify-content: space-between; align-items: center; padding: 12px; background: white; border-radius: 6px;\">\n <div>\n <div style=\"font-size: 18px; font-weight: bold; color: #1f2937;\">${itinerary.flight.origin}</div>\n <div style=\"color: #6b7280; font-size: 13px;\">${formatDate(itinerary.flight.departure)}</div>\n <div style=\"color: #374151; font-size: 14px; font-weight: 600;\">${formatTime(itinerary.flight.departure)}</div>\n </div>\n <div style=\"color: #9ca3af; font-size: 20px;\">→</div>\n <div style=\"text-align: right;\">\n <div style=\"font-size: 18px; font-weight: bold; color: #1f2937;\">${itinerary.flight.destination}</div>\n <div style=\"color: #6b7280; font-size: 13px;\">${formatDate(itinerary.flight.arrival)}</div>\n <div style=\"color: #374151; font-size: 14px; font-weight: 600;\">${formatTime(itinerary.flight.arrival)}</div>\n </div>\n </div>\n <div style=\"margin-top: 12px; display: flex; justify-content: space-between; align-items: center;\">\n <div style=\"color: #1f2937; font-weight: 600; font-size: 16px;\">${itinerary.flight.priceFormatted}</div>\n <a href=\"${itinerary.flight.bookingLink}\" style=\"background: #3b82f6; color: white; padding: 8px 16px; border-radius: 6px; text-decoration: none; font-size: 14px; font-weight: 600;\">Book Flight</a>\n </div>\n </div>\n \n <!-- Hotel Section -->\n <div style=\"background: #f9fafb; border-radius: 8px; padding: 16px;\">\n <div style=\"display: flex; align-items: center; margin-bottom: 12px;\">\n <span style=\"font-size: 20px; margin-right: 8px;\">🏨</span>\n <h3 style=\"margin: 0; color: #374151; font-size: 16px;\">Hotel Details</h3>\n </div>\n <div style=\"margin-bottom: 12px;\">\n <div style=\"font-size: 16px; font-weight: bold; color: #1f2937; margin-bottom: 4px;\">${itinerary.hotel.name}</div>\n <div style=\"color: #6b7280; font-size: 13px; margin-bottom: 4px;\">${itinerary.hotel.address}</div>\n <div style=\"color: #f59e0b; font-size: 14px;\">⭐ ${itinerary.hotel.rating.toFixed(1)} / 10</div>\n </div>\n <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 12px; padding: 12px; background: white; border-radius: 6px; margin-bottom: 12px;\">\n <div>\n <div style=\"color: #6b7280; font-size: 12px; margin-bottom: 4px;\">Nightly Rate</div>\n <div style=\"color: #1f2937; font-weight: 600;\">${formatCurrency(itinerary.hotel.pricePerNight)}</div>\n </div>\n <div>\n <div style=\"color: #6b7280; font-size: 12px; margin-bottom: 4px;\">Total (${itinerary.hotel.nights} nights)</div>\n <div style=\"color: #1f2937; font-weight: 600;\">${formatCurrency(itinerary.hotel.totalPrice)}</div>\n </div>\n </div>\n <div style=\"display: flex; justify-content: space-between; align-items: center;\">\n <div style=\"color: #6b7280; font-size: 13px;\">${formatDate(searchParams.checkInDate)} - ${formatDate(searchParams.checkOutDate)}</div>\n <a href=\"${itinerary.hotel.bookingLink}\" style=\"background: #10b981; color: white; padding: 8px 16px; border-radius: 6px; text-decoration: none; font-size: 14px; font-weight: 600;\">Book Hotel</a>\n </div>\n </div>\n </div>\n `;\n }).join('');\n}\n\n// Generate comparison table\nfunction generateComparisonTable() {\n const rows = itineraries.map((itinerary, index) => `\n <tr style=\"${index % 2 === 0 ? 'background: #f9fafb;' : 'background: white;'}\">\n <td style=\"padding: 12px; text-align: center; font-weight: 600; color: #1f2937;\">${index + 1}</td>\n <td style=\"padding: 12px; color: #374151;\">${itinerary.flight.airline}</td>\n <td style=\"padding: 12px; color: #374151;\">${itinerary.hotel.name}</td>\n <td style=\"padding: 12px; text-align: right; color: #6b7280;\">${itinerary.flight.priceFormatted}</td>\n <td style=\"padding: 12px; text-align: right; color: #6b7280;\">${formatCurrency(itinerary.hotel.totalPrice)}</td>\n <td style=\"padding: 12px; text-align: right; font-weight: bold; color: #10b981; font-size: 16px;\">${formatCurrency(itinerary.totalPrice)}</td>\n </tr>\n `).join('');\n \n return `\n <table style=\"width: 100%; border-collapse: collapse; margin: 24px 0; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.05);\">\n <thead>\n <tr style=\"background: #1f2937; color: white;\">\n <th style=\"padding: 14px; text-align: center; font-weight: 600;\">#</th>\n <th style=\"padding: 14px; text-align: left; font-weight: 600;\">Flight</th>\n <th style=\"padding: 14px; text-align: left; font-weight: 600;\">Hotel</th>\n <th style=\"padding: 14px; text-align: right; font-weight: 600;\">Flight Price</th>\n <th style=\"padding: 14px; text-align: right; font-weight: 600;\">Hotel Price</th>\n <th style=\"padding: 14px; text-align: right; font-weight: 600;\">Total</th>\n </tr>\n </thead>\n <tbody>\n ${rows}\n </tbody>\n </table>\n `;\n}\n\n// Generate complete HTML email\nconst htmlEmail = `\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Your Travel Itinerary Options</title>\n</head>\n<body style=\"margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background: #f3f4f6;\">\n <div style=\"max-width: 700px; margin: 0 auto; padding: 20px;\">\n \n <!-- Header -->\n <div style=\"background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 12px; padding: 32px; text-align: center; margin-bottom: 24px; color: white;\">\n <h1 style=\"margin: 0 0 12px 0; font-size: 32px; font-weight: bold;\">✈️ Your Travel Options</h1>\n <p style=\"margin: 0; font-size: 18px; opacity: 0.95;\">${searchParams.departure} → ${searchParams.destination}</p>\n <p style=\"margin: 8px 0 0 0; font-size: 14px; opacity: 0.9;\">${formatDate(searchParams.checkInDate)} - ${formatDate(searchParams.checkOutDate)}</p>\n </div>\n \n <!-- Summary Stats -->\n <div style=\"background: white; border-radius: 12px; padding: 20px; margin-bottom: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);\">\n <div style=\"display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; text-align: center;\">\n <div>\n <div style=\"color: #6b7280; font-size: 12px; margin-bottom: 4px;\">OPTIONS FOUND</div>\n <div style=\"font-size: 24px; font-weight: bold; color: #3b82f6;\">${itineraries.length}</div>\n </div>\n <div>\n <div style=\"color: #6b7280; font-size: 12px; margin-bottom: 4px;\">BEST PRICE</div>\n <div style=\"font-size: 24px; font-weight: bold; color: #10b981;\">${formatCurrency(itineraries[0].totalPrice)}</div>\n </div>\n <div>\n <div style=\"color: #6b7280; font-size: 12px; margin-bottom: 4px;\">MAX SAVINGS</div>\n <div style=\"font-size: 24px; font-weight: bold; color: #f59e0b;\">${formatCurrency(itineraries[0].savings)}</div>\n </div>\n </div>\n </div>\n \n <!-- Itinerary Cards -->\n <div>\n <h2 style=\"color: #1f2937; font-size: 22px; margin-bottom: 16px;\">📋 Recommended Packages</h2>\n ${generateItineraryCards()}\n </div>\n \n <!-- Comparison Table -->\n <div style=\"margin-top: 32px;\">\n <h2 style=\"color: #1f2937; font-size: 22px; margin-bottom: 16px;\">📊 Quick Comparison</h2>\n ${generateComparisonTable()}\n </div>\n \n <!-- Footer -->\n <div style=\"background: #f9fafb; border-radius: 12px; padding: 20px; margin-top: 24px; text-align: center; color: #6b7280; font-size: 13px;\">\n <p style=\"margin: 0 0 8px 0;\">✨ Generated by Smart Travel Itinerary System</p>\n <p style=\"margin: 0;\">Analyzed ${totalCombinations} combinations from ${flightsFound} flights and ${hotelsFound} hotels</p>\n <p style=\"margin: 8px 0 0 0; font-size: 11px; color: #9ca3af;\">Prices are subject to availability and may change</p>\n </div>\n \n </div>\n</body>\n</html>\n`;\n\nreturn [{\n json: {\n subject: `🎉 ${itineraries.length} Travel Options: ${searchParams.departure} → ${searchParams.destination}`,\n htmlBody: htmlEmail,\n recipient: searchParams.notificationEmail,\n itinerariesCount: itineraries.length,\n bestPrice: formatCurrency(itineraries[0].totalPrice),\n searchParams: searchParams\n }\n}];"
},
"typeVersion": 2
},
{
"id": "7ec3fb5b-a73e-4e99-9019-0b241a2fe736",
"name": "✉️ Send via Gmail",
"type": "n8n-nodes-base.gmail",
"position": [
448,
-496
],
"webhookId": "d0670a84-770f-4fe9-9be4-040e3b63b1f9",
"parameters": {
"sendTo": "={{ $json.recipient }}",
"message": "={{ $json.htmlBody }}",
"options": {},
"subject": "={{ $json.subject }}"
},
"typeVersion": 2.1
},
{
"id": "ecdc9e1c-ae6d-4094-af8f-d9a9c4bc102c",
"name": "📤 Webhook Response",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
608,
-496
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "={{ {\n \"success\": true,\n \"message\": \"Travel itinerary email sent successfully!\",\n \"itinerariesGenerated\": $json.itinerariesCount,\n \"bestPrice\": $json.bestPrice,\n \"sentTo\": $json.recipient,\n \"searchDetails\": {\n \"from\": $json.searchParams.departure,\n \"to\": $json.searchParams.destination,\n \"checkIn\": $json.searchParams.checkInDate,\n \"checkOut\": $json.searchParams.checkOutDate\n },\n \"timestamp\": new Date().toISOString()\n} }}"
},
"typeVersion": 1.1
},
{
"id": "b43f6af3-042e-4258-8006-04cb17fade14",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1360,
-496
],
"parameters": {
"width": 720,
"height": 336,
"content": "## Introduction\nAutomates travel itinerary creation by searching flights and hotels via APIs, then uses AI to generate personalized recommendations delivered as HTML emails through Gmail.\n\n## How It Works\nWebhook receives travel requests, searches Skyscanner and Booking.com APIs in parallel, merges results, uses AI to create optimized itineraries, formats as HTML email, sends via Gmail.\n\n## Workflow Template\nWebhook → Parse & Validate → Parallel Searches (Flights: Skyscanner | Hotels: Booking.com) → Merge Data → AI Generate Itinerary → Format HTML Email → Send Gmail → Webhook Response\n\n\n\n"
},
"typeVersion": 1
},
{
"id": "b27a8a26-8e23-4b58-945c-01bf5d4e8fe9",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
16,
-352
],
"parameters": {
"color": 3,
"width": 752,
"height": 528,
"content": "## Setup Instructions\n1. API Configuration: Add Skyscanner and Booking.com API credentials in n8n.\n2. Gmail Setup: Configure OAuth2 authentication.\n3. Customization: Copy webhook URL, adjust validation rules, modify AI prompts and HTML template.\n## Prerequisites\n- Skyscanner API key\n- Booking.com API credentials\n- Gmail with OAuth2\n- n8n instance\n## Use Cases\n- Personal vacation planning\n- Business travel arrangements\n## Customization\n- Add APIs (Kiwi, Expedia)\n- Filter by budget, Modify email design\n## Benefits\n- Saves 2-3 hours per trip\n- Real-time pricing comparison\n"
},
"typeVersion": 1
},
{
"id": "2d4004f8-8e1c-477c-8669-6facc12b7d85",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1360,
-128
],
"parameters": {
"color": 6,
"width": 720,
"height": 224,
"content": "## Workflow Steps\n1. Trigger & Validate: Webhook receives request, extracts destination/dates/budget/preferences, validates data, converts to API parameters.\n2. Parallel Search: Skyscanner fetches flights with price/duration/airline. Booking.com retrieves hotels with ratings/pricing. Merge combines both into single JSON object.\n3. AI Generation: AI analyzes merged data, evaluates by price/duration/rating, creates itinerary with daily schedule, pairings, costs, and rankings.\n4. Delivery: Converts JSON to HTML email with tables and booking links. Gmail sends email. Webhook confirms success."
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "5ed5ab9a-b808-494a-9bac-8919d45e814c",
"connections": {
"✉️ Send via Gmail": {
"main": [
[
{
"node": "📤 Webhook Response",
"type": "main",
"index": 0
}
]
]
},
"🎨 Format HTML Email": {
"main": [
[
{
"node": "✉️ Send via Gmail",
"type": "main",
"index": 0
}
]
]
},
"📥 Travel Request Webhook": {
"main": [
[
{
"node": "📝 Parse & Validate Inputs",
"type": "main",
"index": 0
}
]
]
},
"📝 Parse & Validate Inputs": {
"main": [
[
{
"node": "✈️ Search Flights (Skyscanner)",
"type": "main",
"index": 0
},
{
"node": "🏨 Search Hotels (Booking.com)",
"type": "main",
"index": 0
}
]
]
},
"🔀 Merge Flight & Hotel Data": {
"main": [
[
{
"node": "🧮 Generate Itinerary Combinations",
"type": "main",
"index": 0
}
]
]
},
"🏨 Search Hotels (Booking.com)": {
"main": [
[
{
"node": "🔀 Merge Flight & Hotel Data",
"type": "main",
"index": 1
}
]
]
},
"✈️ Search Flights (Skyscanner)": {
"main": [
[
{
"node": "🔀 Merge Flight & Hotel Data",
"type": "main",
"index": 0
}
]
]
},
"🧮 Generate Itinerary Combinations": {
"main": [
[
{
"node": "🎨 Format HTML Email",
"type": "main",
"index": 0
}
]
]
}
}
}