AutomationFlowsAI & RAG › AI Travel Itinerary Generator with Skyscanner & Gmail

AI Travel Itinerary Generator with Skyscanner & Gmail

Original n8n title: Ai-optimized Travel Itinerary Generator with Skyscanner, Booking.com and Gmail

ByCheng Siong Chin @cschin on n8n.io

Automates travel planning by aggregating flights, hotels, activities, and weather via APIs, then uses AI to generate professional itineraries delivered through Gmail and Slack.

Webhook trigger★★★★☆ complexityAI-powered18 nodesHTTP RequestGmailAgentSlackOpenRouter Chat
AI & RAG Trigger: Webhook Nodes: 18 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #10352 — 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 →

Download .json
{
  "id": "Yi6ZHj09hQ6aoCt2",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Qwen3-VL-8B-Thinking Travel Planner: Search-->Compare-->Email Best Itineraries",
  "tags": [],
  "nodes": [
    {
      "id": "f3a23f51-4039-4a08-bad5-d9e1f475e8af",
      "name": "Webhook - Travel Request",
      "type": "n8n-nodes-base.webhook",
      "position": [
        2832,
        -256
      ],
      "parameters": {
        "path": "travel-search",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "65e7017e-5242-4875-bf24-661fc68eb771",
      "name": "Extract Request Data",
      "type": "n8n-nodes-base.set",
      "position": [
        2992,
        -256
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a1",
              "name": "destination",
              "type": "string",
              "value": "={{ $json.body.destination || 'Shanghai' }}"
            },
            {
              "id": "a2",
              "name": "departureCity",
              "type": "string",
              "value": "={{ $json.body.departureCity }}"
            },
            {
              "id": "a3",
              "name": "checkInDate",
              "type": "string",
              "value": "={{ $json.body.checkInDate }}"
            },
            {
              "id": "a4",
              "name": "checkOutDate",
              "type": "string",
              "value": "={{ $json.body.checkOutDate }}"
            },
            {
              "id": "a5",
              "name": "travelers",
              "type": "number",
              "value": "={{ $json.body.travelers || 1 }}"
            },
            {
              "id": "a6",
              "name": "email",
              "type": "string",
              "value": "={{ $json.body.email }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "51891077-df8a-45db-b575-0585f52aa1db",
      "name": "Search Flights - Skyscanner",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3216,
        -640
      ],
      "parameters": {
        "url": "https://skyscanner-api.p.rapidapi.com/v3/flights/live/search/create",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "query",
              "value": "={{ {\"market\":\"US\",\"locale\":\"en-US\",\"currency\":\"USD\",\"queryLegs\":[{\"originPlace\":{\"queryPlace\":{\"iata\":$json.departureCity}},\"destinationPlace\":{\"queryPlace\":{\"iata\":$json.destination}},\"date\":{\"year\":$json.checkInDate.split('-')[0],\"month\":$json.checkInDate.split('-')[1],\"day\":$json.checkInDate.split('-')[2]}}],\"adults\":$json.travelers,\"cabinClass\":\"CABIN_CLASS_ECONOMY\"} }}"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "X-RapidAPI-Key",
              "value": "={{ $credentials.rapidApiKey }}"
            },
            {
              "name": "X-RapidAPI-Host",
              "value": "skyscanner-api.p.rapidapi.com"
            }
          ]
        },
        "nodeCredentialType": "httpHeaderAuth"
      },
      "typeVersion": 4.2
    },
    {
      "id": "953e3d0b-528d-41ba-b7ba-4fc7363c703f",
      "name": "Search Hotels - Booking.com",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3216,
        -448
      ],
      "parameters": {
        "url": "https://booking-com.p.rapidapi.com/v1/hotels/search",
        "options": {},
        "sendQuery": true,
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "queryParameters": {
          "parameters": [
            {
              "name": "dest_type",
              "value": "city"
            },
            {
              "name": "dest_id",
              "value": "={{ $('Extract Request Data').item.json.destination }}"
            },
            {
              "name": "checkin_date",
              "value": "={{ $('Extract Request Data').item.json.checkInDate }}"
            },
            {
              "name": "checkout_date",
              "value": "={{ $('Extract Request Data').item.json.checkOutDate }}"
            },
            {
              "name": "adults_number",
              "value": "={{ $('Extract Request Data').item.json.travelers }}"
            },
            {
              "name": "order_by",
              "value": "price"
            },
            {
              "name": "units",
              "value": "metric"
            },
            {
              "name": "room_number",
              "value": "1"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "X-RapidAPI-Key",
              "value": "={{ $credentials.rapidApiKey }}"
            },
            {
              "name": "X-RapidAPI-Host",
              "value": "booking-com.p.rapidapi.com"
            }
          ]
        },
        "nodeCredentialType": "httpHeaderAuth"
      },
      "typeVersion": 4.2
    },
    {
      "id": "133388c8-6fe6-42cb-94b7-f76bf0cb4b2e",
      "name": "Send Email via Gmail",
      "type": "n8n-nodes-base.gmail",
      "position": [
        4480,
        64
      ],
      "parameters": {
        "sendTo": "={{ $('Webhook - Travel Request').item.json.body.email }}",
        "message": "={{ $json.html }}",
        "options": {},
        "subject": "\ud83e\udd16 AI-Optimized Travel Itinerary: {{ $('Extract Request Data').item.json.destination }} Trip"
      },
      "typeVersion": 2.1
    },
    {
      "id": "5fcf6271-c0fd-430b-858e-4a462e98b648",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        4784,
        64
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ { \"success\": true, \"message\": \"AI-optimized travel itinerary sent successfully!\", \"itinerariesCount\": $('AI Score & Recommendations').all().length, \"email\": $('Extract Request Data').item.json.email, \"bestDeal\": { \"price\": $('AI Score & Recommendations').first().json.totalPrice, \"aiScore\": $('AI Score & Recommendations').first().json.aiScore } } }}"
      },
      "typeVersion": 1
    },
    {
      "id": "2aedf5b8-536b-44bc-9b6d-3b80daa2c6c0",
      "name": "AI Agent - Itinerary Optimizer",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        3808,
        -496
      ],
      "parameters": {
        "options": {
          "systemMessage": "You are a travel expert. Analyze the provided flight and hotel combinations and provide a detailed recommendation score (0-100) for each itinerary based on:\n1. Total price value\n2. Flight convenience (stops, duration, departure times)\n3. Hotel quality (rating, reviews, location)\n4. Overall trip experience\n\nFor each itinerary, return:\n- score (0-100)\n- reasoning (2-3 sentences)\n- highlights (bullet points)\n- warnings (if any)\n\nFormat as JSON array."
        }
      },
      "typeVersion": 1
    },
    {
      "id": "759f481f-598a-4bff-a3d1-3cb3310ecb88",
      "name": "Search Alternative Flights - Kiwi",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3216,
        -256
      ],
      "parameters": {
        "url": "https://api.tequila.kiwi.com/v2/search",
        "options": {},
        "authentication": "predefinedCredentialType"
      },
      "typeVersion": 1
    },
    {
      "id": "63c46681-a628-4bd8-aab0-a226ab57e703",
      "name": "Get Weather Forecast",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3216,
        -64
      ],
      "parameters": {
        "url": "https://api.openweathermap.org/data/2.5/forecast",
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "15404c5f-d821-4455-abf6-a08db26baee3",
      "name": "Search Local Activities - Viator",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3216,
        128
      ],
      "parameters": {
        "url": "https://viator-api.p.rapidapi.com/search",
        "options": {},
        "authentication": "predefinedCredentialType"
      },
      "typeVersion": 1
    },
    {
      "id": "27b5a21e-02a6-43e2-b1b9-a7bd5074dd29",
      "name": "Merge All Data Sources",
      "type": "n8n-nodes-base.code",
      "position": [
        3440,
        -256
      ],
      "parameters": {
        "jsCode": "const skyscannerFlights = $input.item(0).json;\nconst kiwiFlights = $input.item(1).json;\nconst bookingHotels = $input.item(2).json;\nconst weather = $input.item(3).json;\nconst activities = $input.item(4).json;\nconst requestData = $('Extract Request Data').first().json;\n\nconst allFlightData = {\n  skyscanner: skyscannerFlights,\n  kiwi: kiwiFlights,\n  weather: weather,\n  activities: activities\n};\n\nconst hotelData = bookingHotels;\n\nreturn [{\n  json: {\n    flights: allFlightData,\n    hotels: hotelData,\n    request: requestData\n  }\n}];"
      },
      "typeVersion": 1
    },
    {
      "id": "d460fa9d-9991-4f29-befc-caff18a9cf38",
      "name": "Enhanced Itinerary Builder",
      "type": "n8n-nodes-base.code",
      "position": [
        3616,
        -256
      ],
      "parameters": {
        "jsCode": "const mergedData = $input.first().json;\nconst requestData = mergedData.request;\n\n// Parse Skyscanner flights\nconst skyscannerFlights = [];\nif (mergedData.flights.skyscanner.itineraries?.results) {\n  mergedData.flights.skyscanner.itineraries.results.slice(0, 8).forEach((flight, i) => {\n    skyscannerFlights.push({\n      id: `sky_${i}`,\n      source: 'Skyscanner',\n      airline: flight.legs[0]?.carriers?.marketing[0]?.name || 'Unknown',\n      departureTime: flight.legs[0]?.departure || '',\n      arrivalTime: flight.legs[0]?.arrival || '',\n      duration: flight.legs[0]?.duration || 0,\n      stops: flight.legs[0]?.stopCount || 0,\n      price: flight.pricing?.price?.amount || 0,\n      currency: flight.pricing?.price?.currency || 'USD',\n      bookingLink: flight.deeplink || '#'\n    });\n  });\n}\n\n// Parse Kiwi flights\nconst kiwiFlights = [];\nif (mergedData.flights.kiwi.data) {\n  mergedData.flights.kiwi.data.slice(0, 8).forEach((flight, i) => {\n    kiwiFlights.push({\n      id: `kiwi_${i}`,\n      source: 'Kiwi.com',\n      airline: flight.airlines?.[0] || 'Unknown',\n      departureTime: new Date(flight.local_departure).toISOString(),\n      arrivalTime: new Date(flight.local_arrival).toISOString(),\n      duration: flight.duration?.total || 0,\n      stops: (flight.route?.length || 1) - 1,\n      price: flight.price || 0,\n      currency: flight.currency || 'USD',\n      bookingLink: flight.deep_link || '#'\n    });\n  });\n}\n\nconst allFlights = [...skyscannerFlights, ...kiwiFlights].sort((a,b) => a.price - b.price);\n\n// Parse hotels\nconst hotels = [];\nif (mergedData.hotels.result) {\n  mergedData.hotels.result.slice(0, 10).forEach((hotel, i) => {\n    hotels.push({\n      id: `hotel_${i}`,\n      name: hotel.hotel_name || 'Unknown',\n      address: hotel.address || '',\n      rating: hotel.review_score || 0,\n      reviewCount: hotel.review_nr || 0,\n      price: hotel.min_total_price || 0,\n      pricePerNight: hotel.price_breakdown?.gross_price || 0,\n      currency: hotel.currency_code || 'USD',\n      amenities: hotel.unit_configuration_label || '',\n      distanceToCenter: hotel.distance || '',\n      bookingLink: hotel.url || '#'\n    });\n  });\n}\n\n// Weather summary\nlet weatherSummary = 'No weather data';\nif (mergedData.flights.weather?.list) {\n  const temps = mergedData.flights.weather.list.slice(0, 8).map(w => w.main.temp);\n  const avgTemp = (temps.reduce((a,b)=>a+b,0)/temps.length).toFixed(1);\n  const conditions = mergedData.flights.weather.list[0]?.weather[0]?.description || 'N/A';\n  weatherSummary = `${avgTemp}\u00b0C, ${conditions}`;\n}\n\n// Activities\nconst activities = [];\nif (mergedData.flights.activities?.data) {\n  mergedData.flights.activities.data.slice(0, 10).forEach((act, i) => {\n    activities.push({\n      id: `act_${i}`,\n      name: act.title || 'Activity',\n      description: act.description || '',\n      rating: act.rating || 0,\n      reviewCount: act.reviewCount || 0,\n      price: act.price?.amount || 0,\n      currency: act.price?.currency || 'USD',\n      duration: act.duration || '',\n      bookingLink: act.productUrl || '#'\n    });\n  });\n}\n\n// Create enhanced itineraries (top 8)\nconst itineraries = [];\nfor (let f = 0; f < Math.min(4, allFlights.length); f++) {\n  for (let h = 0; h < Math.min(2, hotels.length); h++) {\n    if (itineraries.length >= 8) break;\n    \n    const flight = allFlights[f];\n    const hotel = hotels[h];\n    const selectedActivities = activities.slice(0, 3);\n    \n    const totalPrice = flight.price + hotel.price + selectedActivities.reduce((sum, act) => sum + act.price, 0);\n    \n    itineraries.push({\n      id: `itinerary_${itineraries.length + 1}`,\n      flight: flight,\n      hotel: hotel,\n      activities: selectedActivities,\n      weather: weatherSummary,\n      totalPrice: totalPrice,\n      currency: flight.currency,\n      destination: requestData.destination,\n      departureCity: requestData.departureCity,\n      checkInDate: requestData.checkInDate,\n      checkOutDate: requestData.checkOutDate,\n      travelers: requestData.travelers\n    });\n  }\n  if (itineraries.length >= 8) break;\n}\n\nitineraries.sort((a, b) => a.totalPrice - b.totalPrice);\n\nreturn itineraries.slice(0, 8).map(it => ({ json: it }));"
      },
      "typeVersion": 1
    },
    {
      "id": "33047f44-9726-4f2f-a029-c7e71c8aceb8",
      "name": "AI Score & Recommendations",
      "type": "n8n-nodes-base.code",
      "position": [
        4128,
        64
      ],
      "parameters": {
        "jsCode": "const itineraries = $input.all().map(item => item.json);\nconst aiAnalysis = $('AI Agent - Itinerary Optimizer').first().json;\n\nlet aiScores = [];\ntry {\n  aiScores = typeof aiAnalysis.output === 'string' ? JSON.parse(aiAnalysis.output) : aiAnalysis.output;\n} catch (e) {\n  aiScores = itineraries.map((_, i) => ({\n    score: 85 - i * 5,\n    reasoning: 'AI analysis pending',\n    highlights: ['Competitive pricing', 'Good availability'],\n    warnings: []\n  }));\n}\n\nconst enrichedItineraries = itineraries.map((itinerary, index) => {\n  const aiData = aiScores[index] || { score: 75, reasoning: 'Standard option', highlights: [], warnings: [] };\n  return {\n    ...itinerary,\n    aiScore: aiData.score || 75,\n    aiReasoning: aiData.reasoning || 'Good option',\n    aiHighlights: aiData.highlights || [],\n    aiWarnings: aiData.warnings || []\n  };\n});\n\nenrichedItineraries.sort((a, b) => b.aiScore - a.aiScore);\n\nreturn enrichedItineraries.map(it => ({ json: it }));"
      },
      "typeVersion": 1
    },
    {
      "id": "015b10c8-ad0a-4f34-8855-e1366cde7658",
      "name": "Generate Premium HTML Email",
      "type": "n8n-nodes-base.code",
      "position": [
        4288,
        64
      ],
      "parameters": {
        "jsCode": "const itineraries = $input.all().map(item => item.json);\n\nif (itineraries.length === 0) {\n  return [{ json: { html: '<html><body><h2>No Results</h2></body></html>' } }];\n}\n\nconst first = itineraries[0];\nlet html = `\n<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<style>\n  body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; color: #1a1a1a; max-width: 1000px; margin: 0 auto; padding: 20px; background: #f8f9fa; }\n  .container { background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }\n  .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 40px 30px; text-align: center; }\n  .header h1 { margin: 0; font-size: 32px; font-weight: 700; }\n  .summary { background: #f0f4ff; padding: 25px 30px; border-left: 5px solid #667eea; margin: 20px; }\n  .summary strong { color: #667eea; }\n  .itinerary { border: 2px solid #e0e0e0; margin: 20px; border-radius: 10px; overflow: hidden; transition: transform 0.2s; }\n  .itinerary:hover { transform: translateY(-3px); box-shadow: 0 6px 20px rgba(0,0,0,0.15); }\n  .best-deal { border-color: #ffd700; background: linear-gradient(to right, #fff9e6, #ffffff); }\n  .best-deal .itinerary-header { background: linear-gradient(135deg, #ffd700, #ffed4e); }\n  .itinerary-header { background: #667eea; color: white; padding: 20px; display: flex; justify-content: space-between; align-items: center; }\n  .ai-score { background: white; color: #667eea; padding: 8px 20px; border-radius: 20px; font-weight: bold; font-size: 18px; }\n  .section { padding: 25px; }\n  .section-title { color: #667eea; font-size: 20px; font-weight: 600; margin-bottom: 15px; border-bottom: 2px solid #e0e0e0; padding-bottom: 8px; }\n  .detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin: 15px 0; }\n  .detail-item { padding: 12px; background: #f8f9fa; border-radius: 6px; }\n  .detail-label { color: #666; font-size: 13px; margin-bottom: 5px; }\n  .detail-value { color: #1a1a1a; font-weight: 600; font-size: 15px; }\n  .activities-list { list-style: none; padding: 0; }\n  .activities-list li { padding: 12px; margin: 8px 0; background: #f0f4ff; border-left: 4px solid #667eea; border-radius: 4px; }\n  .ai-insights { background: linear-gradient(135deg, #e0f7fa, #f0f4ff); padding: 20px; border-radius: 8px; margin: 15px 0; }\n  .highlight { color: #2e7d32; }\n  .warning { color: #d32f2f; }\n  .price-box { text-align: center; background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 25px; border-radius: 8px; margin: 20px 0; }\n  .price { font-size: 42px; font-weight: 700; }\n  .button { display: inline-block; padding: 14px 28px; background: linear-gradient(135deg, #667eea, #764ba2); color: white; text-decoration: none; border-radius: 25px; margin: 8px; font-weight: 600; transition: all 0.3s; }\n  .button:hover { transform: scale(1.05); box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); }\n  .comparison-table { width: 100%; border-collapse: collapse; margin: 20px 0; }\n  .comparison-table th { background: #667eea; color: white; padding: 15px; text-align: left; font-weight: 600; }\n  .comparison-table td { padding: 12px; border-bottom: 1px solid #e0e0e0; }\n  .comparison-table tr:hover { background: #f8f9fa; }\n  .badge { display: inline-block; padding: 5px 12px; background: #ffd700; color: #1a1a1a; border-radius: 15px; font-size: 12px; font-weight: 700; margin-left: 10px; }\n  .weather-box { background: linear-gradient(135deg, #4facfe, #00f2fe); color: white; padding: 15px; border-radius: 8px; text-align: center; margin: 15px 0; }\n</style>\n</head>\n<body>\n<div class=\"container\">\n  <div class=\"header\">\n    <h1>\u2708\ufe0f Your AI-Optimized Travel Itineraries</h1>\n    <p style=\"margin: 10px 0 0 0; font-size: 18px; opacity: 0.9;\">Powered by Advanced AI Analysis</p>\n  </div>\n\n  <div class=\"summary\">\n    <strong>\ud83d\udccd Route:</strong> ${first.departureCity} \u2192 ${first.destination}<br>\n    <strong>\ud83d\udcc5 Dates:</strong> ${first.checkInDate} to ${first.checkOutDate}<br>\n    <strong>\ud83d\udc65 Travelers:</strong> ${first.travelers}<br>\n    <strong>\ud83c\udf24\ufe0f Weather:</strong> ${first.weather}\n  </div>\n\n  <div style=\"padding: 30px;\">\n    <h2 style=\"color: #667eea; font-size: 28px; margin-bottom: 20px;\">\ud83c\udfc6 Top ${itineraries.length} AI-Ranked Itineraries</h2>\n`;\n\nitineraries.forEach((it, index) => {\n  const isBest = index === 0;\n  html += `\n    <div class=\"itinerary ${isBest ? 'best-deal' : ''}\">\n      <div class=\"itinerary-header\" ${isBest ? 'style=\"background: linear-gradient(135deg, #ffd700, #ffed4e); color: #1a1a1a;\"' : ''}>\n        <div>\n          <h3 style=\"margin: 0; font-size: 24px;\">Option ${index + 1} ${isBest ? '<span class=\"badge\">BEST VALUE</span>' : ''}</h3>\n          <p style=\"margin: 5px 0 0 0; opacity: 0.8;\">AI-Optimized Package</p>\n        </div>\n        <div class=\"ai-score\">\ud83e\udd16 ${it.aiScore}/100</div>\n      </div>\n\n      <div class=\"section\">\n        <div class=\"ai-insights\">\n          <strong style=\"color: #667eea; font-size: 16px;\">\ud83e\udde0 AI Analysis:</strong>\n          <p style=\"margin: 10px 0;\">${it.aiReasoning}</p>\n          ${it.aiHighlights.length > 0 ? `<div class=\"highlight\">\u2713 ${it.aiHighlights.join(' \u2022 ')}</div>` : ''}\n          ${it.aiWarnings.length > 0 ? `<div class=\"warning\">\u26a0 ${it.aiWarnings.join(' \u2022 ')}</div>` : ''}\n        </div>\n\n        <div class=\"section-title\">\u2708\ufe0f Flight Details</div>\n        <div class=\"detail-grid\">\n          <div class=\"detail-item\"><div class=\"detail-label\">Source</div><div class=\"detail-value\">${it.flight.source}</div></div>\n          <div class=\"detail-item\"><div class=\"detail-label\">Airline</div><div class=\"detail-value\">${it.flight.airline}</div></div>\n          <div class=\"detail-item\"><div class=\"detail-label\">Departure</div><div class=\"detail-value\">${it.flight.departureTime}</div></div>\n          <div class=\"detail-item\"><div class=\"detail-label\">Arrival</div><div class=\"detail-value\">${it.flight.arrivalTime}</div></div>\n          <div class=\"detail-item\"><div class=\"detail-label\">Stops</div><div class=\"detail-value\">${it.flight.stops === 0 ? 'Non-stop \u2b50' : it.flight.stops + ' stop(s)'}</div></div>\n          <div class=\"detail-item\"><div class=\"detail-label\">Flight Price</div><div class=\"detail-value\">${it.currency} $${it.flight.price.toFixed(2)}</div></div>\n        </div>\n\n        <div class=\"section-title\">\ud83c\udfe8 Hotel Details</div>\n        <div class=\"detail-grid\">\n          <div class=\"detail-item\"><div class=\"detail-label\">Hotel Name</div><div class=\"detail-value\">${it.hotel.name}</div></div>\n          <div class=\"detail-item\"><div class=\"detail-label\">Rating</div><div class=\"detail-value\">\u2b50 ${it.hotel.rating}/10 (${it.hotel.reviewCount} reviews)</div></div>\n          <div class=\"detail-item\"><div class=\"detail-label\">Location</div><div class=\"detail-value\">${it.hotel.distanceToCenter || 'City Center'}</div></div>\n          <div class=\"detail-item\"><div class=\"detail-label\">Hotel Price</div><div class=\"detail-value\">${it.currency} $${it.hotel.price.toFixed(2)}</div></div>\n        </div>\n\n        ${it.activities.length > 0 ? `\n        <div class=\"section-title\">\ud83c\udfaf Recommended Activities</div>\n        <ul class=\"activities-list\">\n          ${it.activities.map(act => `\n            <li>\n              <strong>${act.name}</strong> ${act.rating > 0 ? `(${act.rating}\u2b50)` : ''}<br>\n              <span style=\"color: #666; font-size: 13px;\">${act.description.substring(0, 100)}...</span><br>\n              <strong style=\"color: #667eea;\">${act.currency} $${act.price.toFixed(2)}</strong> \u2022 ${act.duration}\n            </li>\n          `).join('')}\n        </ul>\n        ` : ''}\n\n        <div class=\"weather-box\">\n          <strong>\ud83c\udf24\ufe0f Weather Forecast:</strong> ${it.weather}\n        </div>\n\n        <div class=\"price-box\">\n          <div style=\"font-size: 16px; margin-bottom: 5px;\">TOTAL PACKAGE PRICE</div>\n          <div class=\"price\">${it.currency} $${it.totalPrice.toFixed(2)}</div>\n          <div style=\"margin-top: 20px;\">\n            <a href=\"${it.flight.bookingLink}\" class=\"button\">Book Flight</a>\n            <a href=\"${it.hotel.bookingLink}\" class=\"button\">Book Hotel</a>\n          </div>\n        </div>\n      </div>\n    </div>\n  `;\n});\n\nhtml += `\n    <div style=\"margin-top: 40px;\">\n      <h2 style=\"color: #667eea; font-size: 24px;\">\ud83d\udcca Price Comparison Matrix</h2>\n      <table class=\"comparison-table\">\n        <tr>\n          <th>Option</th>\n          <th>AI Score</th>\n          <th>Source</th>\n          <th>Flight</th>\n          <th>Hotel</th>\n          <th>Activities</th>\n          <th>Total</th>\n        </tr>\n`;\n\nitineraries.forEach((it, i) => {\n  const actPrice = it.activities.reduce((sum, a) => sum + a.price, 0);\n  html += `\n    <tr ${i === 0 ? 'style=\"background: #fff9e6; font-weight: bold;\"' : ''}>\n      <td>Option ${i + 1} ${i === 0 ? '<span class=\"badge\">BEST</span>' : ''}</td>\n      <td>${it.aiScore}/100</td>\n      <td>${it.flight.source}</td>\n      <td>$${it.flight.price.toFixed(2)}</td>\n      <td>$${it.hotel.price.toFixed(2)}</td>\n      <td>$${actPrice.toFixed(2)}</td>\n      <td style=\"color: #667eea; font-size: 18px; font-weight: bold;\">$${it.totalPrice.toFixed(2)}</td>\n    </tr>\n  `;\n});\n\nhtml += `\n      </table>\n    </div>\n\n    <div style=\"margin-top: 40px; padding: 25px; background: linear-gradient(135deg, #f0f4ff, #e0f7fa); border-radius: 10px;\">\n      <h3 style=\"color: #667eea; margin-top: 0;\">\ud83d\udca1 AI Travel Tips</h3>\n      <ul style=\"line-height: 1.8;\">\n        <li>Book flights and hotels separately for maximum flexibility</li>\n        <li>Weather data is forecast-based; pack accordingly</li>\n        <li>Activities can be booked on-site or in advance</li>\n        <li>Prices are dynamic and subject to change</li>\n        <li>Consider travel insurance for peace of mind</li>\n      </ul>\n    </div>\n  </div>\n</div>\n\n<div style=\"text-align: center; margin-top: 30px; padding: 20px; color: #666; font-size: 13px;\">\n  <p>\ud83e\udd16 This itinerary was generated by AI-powered n8n workflow automation<br>\n  Prices and availability are subject to change \u2022 Book early for best rates</p>\n</div>\n</body>\n</html>\n`;\n\nreturn [{ json: { html, itineraries } }];"
      },
      "typeVersion": 1
    },
    {
      "id": "0551345f-ca44-4adc-8f49-5ddf522f8a74",
      "name": "Send Slack Notification",
      "type": "n8n-nodes-base.slack",
      "position": [
        4656,
        256
      ],
      "parameters": {
        "text": "\ud83c\udf89 New travel itinerary sent!\\n\u2708\ufe0f Route: {{ $('Extract Request Data').item.json.departureCity }} \u2192 {{ $('Extract Request Data').item.json.destination }}\\n\ud83d\udce7 Email: {{ $('Webhook - Travel Request').item.json.body.email }}\\n\ud83c\udfc6 Best Deal: ${{ $('AI Score & Recommendations').first().json.totalPrice.toFixed(2) }}\\n\ud83e\udd16 AI Score: {{ $('AI Score & Recommendations').first().json.aiScore }}/100",
        "channel": "#travel-bookings",
        "attachments": [],
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "typeVersion": 1
    },
    {
      "id": "82bb48ce-ad62-43cc-bb7e-69cf5f55e4c0",
      "name": "OpenRouter Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        3792,
        -320
      ],
      "parameters": {
        "model": "qwen/qwen3-vl-8b-thinking",
        "options": {}
      },
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "e66d7eb9-383f-4ec3-9e76-c33dcfabffdf",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2288,
        -608
      ],
      "parameters": {
        "width": 512,
        "height": 768,
        "content": "## Introduction\nAutomates travel planning by aggregating flights, hotels, activities, and weather via APIs, then uses AI to generate professional itineraries delivered through Gmail and Slack.\n\n## How It Works\nWebhook receives requests, searches APIs (Skyscanner, Booking.com, Kiwi, Viator, weather), merges data, AI builds itineraries, scores options, generates HTML emails, delivers via Gmail/Slack.\n\n## Workflow Template\nWebhook \u2192 Extract \u2192 Parallel Searches (Flights/Hotels/Activities/Weather) \u2192 Merge \u2192 Build Itinerary \u2192 AI Processing \u2192 Score \u2192 Generate HTML \u2192 Gmail \u2192 Slack \u2192 Response\n\n## Workflow Steps\n1. Trigger & Extract: Receives destination, dates, preferences, extracts parameters.\n2. Data Gathering: Parallel APIs fetch flights, hotels, activities, weather, merges responses.\n3. AI Processing: Analyzes data, creates itinerary, ranks recommendations.\n4. Delivery: Generates HTML email, sends via Gmail/Slack, confirms completion.\n\n## Setup Instructions\n1. API Configuration: Add keys for Skyscanner, Booking.com, Kiwi, Viator, OpenWeatherMap, OpenRouter.\n2. Communication: Connect Gmail OAuth2, Slack webhook.\n3. Customization: Adjust endpoints, AI prompts, HTML template, scoring criteria.\n\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "70771bd2-ff0c-46a1-86ae-08d5698b5c56",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4144,
        -544
      ],
      "parameters": {
        "color": 5,
        "width": 352,
        "height": 576,
        "content": "## Prerequisites\n- API keys: Skyscanner, Booking.com, Kiwi, Viator, OpenWeatherMap, OpenRouter\n- Gmail account\n- Slack workspace\n- n8n instance\n## Use Cases\n- Corporate travel planning\n- Vacation itinerary generation\n- Group trip coordination\n\n## Customization\n- Add sources (Airbnb, TripAdvisor)\n- Filter by budget preferences\n- Add PDF generation\n- Customize Slack format\n\n## Benefits\n- Saves 3-5 hours per trip\n- Real-time pricing aggregation\n- AI-powered personalization\n- Automated multi-channel delivery"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "973f16a5-77bb-4015-a5c5-a32efacdddd7",
  "connections": {
    "Generate HTML Email": {
      "main": [
        [
          {
            "node": "Send Email via Gmail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Request Data": {
      "main": [
        [
          {
            "node": "Search Flights - Skyscanner",
            "type": "main",
            "index": 0
          },
          {
            "node": "Search Hotels - Booking.com",
            "type": "main",
            "index": 0
          },
          {
            "node": "Search Alternative Flights - Kiwi",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get Weather Forecast",
            "type": "main",
            "index": 0
          },
          {
            "node": "Search Local Activities - Viator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Weather Forecast": {
      "main": [
        [
          {
            "node": "Merge All Data Sources",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Email via Gmail": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send Slack Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent - Itinerary Optimizer",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Merge All Data Sources": {
      "main": [
        [
          {
            "node": "Enhanced Itinerary Builder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Slack Notification": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook - Travel Request": {
      "main": [
        [
          {
            "node": "Extract Request Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Score & Recommendations": {
      "main": [
        [
          {
            "node": "Generate Premium HTML Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine & Rank Itineraries": {
      "main": [
        [
          {
            "node": "Generate HTML Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Enhanced Itinerary Builder": {
      "main": [
        [
          {
            "node": "AI Agent - Itinerary Optimizer",
            "type": "main",
            "index": 0
          },
          {
            "node": "AI Score & Recommendations",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Premium HTML Email": {
      "main": [
        [
          {
            "node": "Send Email via Gmail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Flights - Skyscanner": {
      "main": [
        [
          {
            "node": "Combine & Rank Itineraries",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge All Data Sources",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Hotels - Booking.com": {
      "main": [
        [
          {
            "node": "Combine & Rank Itineraries",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge All Data Sources",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent - Itinerary Optimizer": {
      "main": [
        [
          {
            "node": "AI Score & Recommendations",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Local Activities - Viator": {
      "main": [
        [
          {
            "node": "Merge All Data Sources",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Alternative Flights - Kiwi": {
      "main": [
        [
          {
            "node": "Merge All Data Sources",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Automates travel planning by aggregating flights, hotels, activities, and weather via APIs, then uses AI to generate professional itineraries delivered through Gmail and Slack.

Source: https://n8n.io/workflows/10352/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

Who is this for? Agencies, consultants, and service providers who conduct discovery calls and need to quickly turn conversations into professional proposals.

Tool Think, Tool Calculator, Agent Tool +18
AI & RAG

leads. Uses supabase, gmail, formTrigger, httpRequest. Webhook trigger; 62 nodes.

Supabase, Gmail, Form Trigger +13
AI & RAG

Scheduled runs collect data from oil markets, global shipping movements, news sources, and official reports. The system performs statistical checks to detect anomalies and volatility shifts. An AI-dri

HTTP Request, Agent, Gmail +3
AI & RAG

🧾 Short Description

Agent, OpenAI Chat, Output Parser Structured +4
AI & RAG

This template uses the Apify LinkedIn Profile Scraper, which is a community node only available in self-hosted n8n installations. The LinkedIn scraping step is optional and can be removed for n8n Clou

HTTP Request, Perplexity, Slack +8