This workflow corresponds to n8n.io template #10354 — we link there as the canonical source.
This workflow follows the Gmail → HTTP Request 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 →
{
"id": "Q9TXNQXxpkuynljZ",
"name": "Smart Travel Package Finder - Search Flights & Hotels with Skyscanner-Booking.com",
"tags": [],
"nodes": [
{
"id": "e1964e90-e7d0-480d-8eed-36dd27d9d648",
"name": "\ud83d\udce5 Travel Request Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
-640,
-384
],
"parameters": {
"path": "travel-search",
"options": {
"rawBody": true
},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2
},
{
"id": "d8b0f0fc-3ab8-4f95-a9c8-6012ad56a9ab",
"name": "\ud83d\udcdd 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": "\u2708\ufe0f 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": "\ud83c\udfe8 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": "\ud83d\udd00 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": "\ud83e\uddee 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": "\ud83c\udfa8 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;\">\ud83c\udfc6 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;\">\u2708\ufe0f</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;\">\u2192</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;\">\ud83c\udfe8</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;\">\u2b50 ${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;\">\u2708\ufe0f Your Travel Options</h1>\n <p style=\"margin: 0; font-size: 18px; opacity: 0.95;\">${searchParams.departure} \u2192 ${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;\">\ud83d\udccb 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;\">\ud83d\udcca 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;\">\u2728 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: `\ud83c\udf89 ${itineraries.length} Travel Options: ${searchParams.departure} \u2192 ${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": "\u2709\ufe0f Send via Gmail",
"type": "n8n-nodes-base.gmail",
"position": [
448,
-496
],
"parameters": {
"sendTo": "={{ $json.recipient }}",
"message": "={{ $json.htmlBody }}",
"options": {},
"subject": "={{ $json.subject }}"
},
"typeVersion": 2.1
},
{
"id": "ecdc9e1c-ae6d-4094-af8f-d9a9c4bc102c",
"name": "\ud83d\udce4 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 \u2192 Parse & Validate \u2192 Parallel Searches (Flights: Skyscanner | Hotels: Booking.com) \u2192 Merge Data \u2192 AI Generate Itinerary \u2192 Format HTML Email \u2192 Send Gmail \u2192 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,
"settings": {
"executionOrder": "v1"
},
"versionId": "5ed5ab9a-b808-494a-9bac-8919d45e814c",
"connections": {
"\u2709\ufe0f Send via Gmail": {
"main": [
[
{
"node": "\ud83d\udce4 Webhook Response",
"type": "main",
"index": 0
}
]
]
},
"\ud83c\udfa8 Format HTML Email": {
"main": [
[
{
"node": "\u2709\ufe0f Send via Gmail",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udce5 Travel Request Webhook": {
"main": [
[
{
"node": "\ud83d\udcdd Parse & Validate Inputs",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udcdd Parse & Validate Inputs": {
"main": [
[
{
"node": "\u2708\ufe0f Search Flights (Skyscanner)",
"type": "main",
"index": 0
},
{
"node": "\ud83c\udfe8 Search Hotels (Booking.com)",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udd00 Merge Flight & Hotel Data": {
"main": [
[
{
"node": "\ud83e\uddee Generate Itinerary Combinations",
"type": "main",
"index": 0
}
]
]
},
"\ud83c\udfe8 Search Hotels (Booking.com)": {
"main": [
[
{
"node": "\ud83d\udd00 Merge Flight & Hotel Data",
"type": "main",
"index": 1
}
]
]
},
"\u2708\ufe0f Search Flights (Skyscanner)": {
"main": [
[
{
"node": "\ud83d\udd00 Merge Flight & Hotel Data",
"type": "main",
"index": 0
}
]
]
},
"\ud83e\uddee Generate Itinerary Combinations": {
"main": [
[
{
"node": "\ud83c\udfa8 Format HTML Email",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Automates travel itinerary creation by searching flights and hotels via APIs, then uses AI to generate personalized recommendations delivered as HTML emails through Gmail.
Source: https://n8n.io/workflows/10354/ — 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.
Automate WhatsApp communication for recruitment agencies with an interactive, structured customer experience. This workflow handles pricing inquiries, request submissions, tracking, complaints, and hu
This template turns Podium's conversation inbox into a full sales CRM with a custom funnel, AI message classification, automated drip follow-ups, daily admin reports, and a live Kanban dashboard. Six
Suspicious_login_detection. Uses postgres, httpRequest, noOp, html. Webhook trigger; 43 nodes.
This n8n workflow is designed for security monitoring and incident response when suspicious login events are detected. It can be initiated either manually from within the n8n UI for testing or automat
This workflow automates a document approval process using Supabase and Gmail. Teams that need structured multi-level document approvals. Companies managing policies, contracts, or proposals. Medical d