{
  "id": "7yP3M8dP7Oezu0ST",
  "name": "flights automation",
  "tags": [],
  "nodes": [
    {
      "id": "ce76759a-0a7b-4692-9c36-0ae8a4836f43",
      "name": "When clicking 'Execute workflow'",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        432,
        400
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "9072e06e-0b00-4aad-ba03-2c10225ce562",
      "name": "all dates full report",
      "type": "n8n-nodes-base.slack",
      "position": [
        2544,
        720
      ],
      "parameters": {
        "text": "={{ \n'\ud83d\udcca *FULL 18-DATE PRICE REPORT \u2014 PHL \u2708\ufe0f DAC* | Round Trip | Economy\\n'\n+ '\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n\\n'\n+ '\ud83d\udcc5 *COMPLETE PRICE CALENDAR \u2014 Jul 1 to Jul 18*\\n'\n+ '\ud83d\udd25 = below $' + $json.TARGET + ' target   |   \ud83d\udc51 = cheapest date\\n\\n'\n+ $json.calendarBlock + '\\n\\n'\n+ '\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n'\n+ '\ud83d\udcb0 *OVERALL CHEAPEST FOUND THIS RUN*\\n'\n+ '\ud83d\udcb5 Price: *$' + $json.currentPrice + ' USD*\\n'\n+ '\ud83d\udcc5 Date:  *' + $json.cheapestDateDisplay + '*\\n'\n+ ($json.lastPrice !== null ? '\ud83d\udcc9 Previous best: $' + $json.lastPrice + ' USD\\n' : '')\n+ $json.trendEmoji + ' Price *' + $json.trendText + '*\\n'\n+ '\ud83d\udcca Last 5 runs: ' + $json.historyLine + '\\n\\n'\n+ '\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n'\n+ '\u2708\ufe0f *CHEAPEST FLIGHT DETAILS (departing ' + $json.cheapestDateDisplay + ')*\\n'\n+ '\ud83c\udff7\ufe0f  Airline:    *' + $json.airline + '*\\n'\n+ '\ud83d\udd22  Flight No:  *' + $json.flightNumbers + '*\\n'\n+ '\ud83d\udccd  From:       *Philadelphia Intl Airport (PHL)*\\n'\n+ '\ud83d\udccd  To:         *Hazrat Shahjalal Intl Airport (DAC)*\\n'\n+ '\ud83d\udeeb  Departure:  *' + $json.depTime + '*\\n'\n+ '\ud83d\udeec  Arrival:    *' + $json.arrTime + '*\\n'\n+ '\u23f1\ufe0f  Duration:   *' + $json.duration + '*\\n'\n+ '\ud83d\udd01  Stops:      *' + $json.stopsLabel + '*\\n'\n+ ($json.layovers ? '\ud83c\udfd9\ufe0f  Layovers:   *' + $json.layovers + '*\\n' : '')\n+ '\ud83d\udcb5  Price:      *$' + $json.currentPrice + ' USD*  |  Economy | Round Trip\\n\\n'\n+ '\ud83c\udfab *<' + $json.bookingLink + '|\ud83d\udc49 BOOK ON GOOGLE FLIGHTS>*\\n\\n'\n+ '\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n'\n+ '\u23f0 Checked: *' + $json.checkedAt + ' ET*\\n'\n+ '\ud83d\udce1 ' + $json.periodLabel + '\\n'\n+ '\ud83d\uddd3\ufe0f Dates scanned: ' + $json.datesChecked + ' | With data: ' + $json.datesWithData\n}}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0ASJMR4MMJ",
          "cachedResultName": "sales-team"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        },
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "e2171594-c99b-467a-93d8-8324a7f17644",
      "name": "Smart Schedule Gate",
      "type": "n8n-nodes-base.code",
      "position": [
        704,
        400
      ],
      "parameters": {
        "jsCode": "const now       = DateTime.now().setZone('America/New_York');\nconst isNight   = now.hour >= 22 || now.hour < 7;\nconst onTheHour = now.minute === 0;\nconst shouldRun = isNight || (!isNight && onTheHour);\n\nreturn [{\n  json: {\n    shouldRun,\n    periodLabel: isNight\n      ? '\ud83c\udf19 Nighttime Mode \u2014 every 10 min'\n      : '\u2600\ufe0f Daytime Mode \u2014 every 1 hour',\n    checkedAt: now.toFormat('yyyy-MM-dd HH:mm:ss')\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "551f2c7a-7a5f-4ce5-8952-5c115eeacc8a",
      "name": "Should Run Now?",
      "type": "n8n-nodes-base.if",
      "position": [
        912,
        400
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-run",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.shouldRun }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "2dcc792a-e586-478e-a82a-d6104f1e7b30",
      "name": "Run an Actor and get dataset",
      "type": "@apify/n8n-nodes-apify.apify",
      "position": [
        1376,
        400
      ],
      "parameters": {
        "memory": 256,
        "actorId": {
          "__rl": true,
          "mode": "list",
          "value": "1dYHRKkEBHBPd0JM7",
          "cachedResultUrl": "https://console.apify.com/actors/1dYHRKkEBHBPd0JM7/input",
          "cachedResultName": "Google Flights API (johnvc/Google-Flights-Data-Scraper-Flight-and-Price-Search)"
        },
        "timeout": {},
        "operation": "Run actor and get dataset",
        "customBody": "={{ JSON.stringify({\n  \"departure_id\": \"PHL\",\n  \"arrival_id\": \"DAC\",\n  \"outbound_date\": $json.departureDate,\n  \"return_date\": DateTime.fromISO($json.departureDate).plus({days: 30}).toFormat('yyyy-MM-dd'),\n  \"adults\": 1,\n  \"currency\": \"USD\",\n  \"hl\": \"en\",\n  \"gl\": \"us\",\n  \"travel_class\": \"1\",\n  \"max_pages\": 1\n}) }}",
        "actorSource": "store",
        "authentication": "apifyOAuth2Api"
      },
      "credentials": {
        "apifyOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "executeOnce": false,
      "typeVersion": 1
    },
    {
      "id": "61818614-9828-4010-8e79-7390fe2bfa37",
      "name": "Below $1,400?",
      "type": "n8n-nodes-base.if",
      "position": [
        2304,
        400
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-below",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.belowTarget }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "b1232ec9-5d31-4de4-881f-e15b7c5206af",
      "name": "Generate dates",
      "type": "n8n-nodes-base.code",
      "position": [
        1168,
        400
      ],
      "parameters": {
        "jsCode": "// ================================================================\n// GENERATE ALL 18 DATES: July 1 to July 18, 2026\n// Each date becomes a separate item \u2014 Apify node runs once per item\n// ================================================================\nconst sd = $getWorkflowStaticData('global');\nsd.periodLabel = $('Smart Schedule Gate').first().json.periodLabel;\nsd.checkedAt   = $('Smart Schedule Gate').first().json.checkedAt;\n\nconst dates = [];\nlet cur = DateTime.fromISO('2026-07-01', { zone: 'America/New_York' });\nconst end = DateTime.fromISO('2026-07-18', { zone: 'America/New_York' });\n\nwhile (cur <= end) {\n  dates.push({\n    json: {\n      departureDate: cur.toFormat('yyyy-MM-dd'),\n      displayDate:   cur.toFormat('EEE, MMM dd')\n    }\n  });\n  cur = cur.plus({ days: 1 });\n}\n\nreturn dates;"
      },
      "typeVersion": 2
    },
    {
      "id": "6eec680a-f911-4527-8596-10ec73413da6",
      "name": "Extract Cheapest for This Date",
      "type": "n8n-nodes-base.code",
      "position": [
        1632,
        400
      ],
      "parameters": {
        "jsCode": "const item = $input.item.json;\n\nconst depDate  = item?.search_parameters?.outbound_date || 'N/A';\nconst dispDate = depDate !== 'N/A'\n  ? DateTime.fromISO(depDate, { zone: 'America/New_York' }).toFormat('EEE, MMM dd')\n  : 'N/A';\n\nconst lowestPrice = item?.price_insights?.lowest_price || null;\n\nlet allFlights = [];\nif (Array.isArray(item.best_flights))  allFlights = allFlights.concat(item.best_flights);\nif (Array.isArray(item.other_flights)) allFlights = allFlights.concat(item.other_flights);\n\nallFlights = allFlights\n  .filter(f => f && f.price !== null && f.price !== undefined)\n  .sort((a, b) => (a.price || 99999) - (b.price || 99999));\n\nif (!allFlights.length || lowestPrice === null) {\n  return [{ json: {\n    departureDate: depDate,\n    displayDate:   dispDate,\n    available:     false,\n    price:         null,\n    airline:       null,\n    flightNumbers: null,\n    depTime:       null,\n    arrTime:       null,\n    duration:      null,\n    stopsLabel:    null,\n    layovers:      null,\n    bookingLink:   null\n  }}];\n}\n\nconst top   = allFlights[0];\nconst legs  = top.flights || [];\nconst first = legs[0] || {};\nconst last  = legs[legs.length - 1] || {};\nconst stops = legs.length > 0 ? legs.length - 1 : 0;\nconst mins  = top.total_duration || 0;\n\nconst layovers = (top.layovers || []).map(l => {\n  const h = Math.floor((l.duration || 0) / 60);\n  const m = (l.duration || 0) % 60;\n  return `${l.name || '?'} (${h}h ${m}m)`;\n}).join(', ');\n\nconst flightNumbers = legs.map(l => l.flight_number || '').filter(Boolean).join(' \u2192 ') || 'N/A';\nconst airline  = first.airline || top.airline || 'Unknown';\nconst depTime  = (first.departure_airport || {}).time || 'N/A';\nconst arrTime  = (last.arrival_airport || {}).time || 'N/A';\nconst duration = mins > 0 ? `${Math.floor(mins / 60)}h ${mins % 60}m` : 'N/A';\nconst bookingLink = top.booking_link || top.bookingLink ||\n  `https://www.google.com/travel/flights/search?q=flights+from+PHL+to+DAC+on+${depDate}&curr=USD&gl=us&hl=en`;\n\nreturn [{ json: {\n  departureDate: depDate,\n  displayDate:   dispDate,\n  available:     true,\n  price:         lowestPrice,\n  airline,\n  flightNumbers,\n  depTime,\n  arrTime,\n  duration,\n  stopsLabel:    stops === 0 ? 'Nonstop \u2708\ufe0f' : `${stops} Stop${stops > 1 ? 's' : ''}`,\n  layovers,\n  bookingLink\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "ee2d8547-c9d9-4f22-b0c9-88aa6e457787",
      "name": "Collect All 18 Date Results",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        1872,
        400
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData"
      },
      "typeVersion": 1
    },
    {
      "id": "cf9af521-f96a-46b4-a59c-8a2eec72e0f1",
      "name": "Find Cheapest & Decide",
      "type": "n8n-nodes-base.code",
      "position": [
        2112,
        400
      ],
      "parameters": {
        "jsCode": "// ================================================================\n// MASTER AGGREGATOR\n// Receives all 18 date results, finds cheapest,\n// builds calendar, computes trend, decides Slack channel\n// ================================================================\nconst TARGET = 1400;\nconst sd     = $getWorkflowStaticData('global');\nconst now    = DateTime.now().setZone('America/New_York');\n\nconst checkedAt   = now.toFormat('yyyy-MM-dd HH:mm:ss');\nconst periodLabel = sd.periodLabel || (now.hour >= 22 || now.hour < 7\n  ? '\ud83c\udf19 Nighttime Mode \u2014 every 10 min'\n  : '\u2600\ufe0f Daytime Mode \u2014 every 1 hour');\n\n// Aggregate node wraps all 18 items into one item with a 'data' array\nconst raw      = $input.first().json;\nconst allDates = Array.isArray(raw.data) ? raw.data : [];\n\nconst available = allDates\n  .filter(d => d.available && d.price !== null)\n  .sort((a, b) => a.price - b.price);\n\nif (!available.length) {\n  return [{ json: {\n    hasData:     false,\n    belowTarget: false,\n    checkedAt,\n    periodLabel,\n    calendarBlock: '\u274c No data returned for any date.',\n    error: 'No priced flights found across Jul 1\u201318.'\n  }}];\n}\n\nconst cheapest     = available[0];\nconst currentPrice = cheapest.price;\n\n// Build price calendar\nconst byDate = {};\nfor (const d of allDates) {\n  if (d.available && d.price !== null) byDate[d.departureDate] = d;\n}\n\nconst calLines = [];\nlet cur = DateTime.fromISO('2026-07-01', { zone: 'America/New_York' });\nconst endDt = DateTime.fromISO('2026-07-18', { zone: 'America/New_York' });\nwhile (cur <= endDt) {\n  const key   = cur.toFormat('yyyy-MM-dd');\n  const disp  = cur.toFormat('EEE, MMM dd');\n  const entry = byDate[key];\n  if (entry) {\n    const isBest = key === cheapest.departureDate;\n    const fire   = entry.price < TARGET ? ' \ud83d\udd25' : '';\n    const crown  = isBest ? ' \ud83d\udc51 *CHEAPEST*' : '';\n    calLines.push(`\u2022 *${disp}*: *$${entry.price} USD*${fire}${crown}`);\n  } else {\n    calLines.push(`\u2022 *${disp}*: \u274c No data`);\n  }\n  cur = cur.plus({ days: 1 });\n}\nconst calendarBlock = calLines.join('\\n');\n\n// Price trend\nif (!sd.priceHistory) sd.priceHistory = [];\nconst lastPrice   = sd.lastPrice    || null;\nconst lastAlerted = sd.lastAlertedAt || 0;\n\nlet priceTrend = 'SAME';\nif (lastPrice !== null) {\n  if (Math.round(currentPrice * 100) < Math.round(lastPrice * 100)) priceTrend = 'DOWN';\n  else if (Math.round(currentPrice * 100) > Math.round(lastPrice * 100)) priceTrend = 'UP';\n}\n\nconst priceDiff  = lastPrice !== null ? Math.round((lastPrice - currentPrice) * 100) / 100 : 0;\nconst trendEmoji = priceTrend === 'DOWN' ? '\ud83d\udcc9' : priceTrend === 'UP' ? '\ud83d\udcc8' : '\u27a1\ufe0f';\nconst trendText  = priceTrend === 'DOWN'\n  ? `dropped by $${Math.abs(priceDiff)} since last check`\n  : priceTrend === 'UP'\n    ? `rose by $${Math.abs(priceDiff)} since last check`\n    : 'unchanged since last check';\n\nsd.priceHistory.push({ price: currentPrice, at: checkedAt });\nif (sd.priceHistory.length > 5) sd.priceHistory.shift();\nconst historyLine = sd.priceHistory.map(h => `$${h.price}`).join(' \u2192 ');\n\n// Alert decision\nconst belowTarget = currentPrice < TARGET;\nlet shouldAlert   = false;\nlet alertReason   = '';\n\nif (belowTarget) {\n  if (lastPrice === null) {\n    shouldAlert = true; alertReason = 'FIRST TIME BELOW TARGET';\n  } else if (priceTrend === 'DOWN') {\n    shouldAlert = true; alertReason = 'PRICE DROPPED BELOW TARGET';\n  } else {\n    const hrs = (Date.now() - lastAlerted) / (1000 * 60 * 60);\n    if (hrs >= 2) { shouldAlert = true; alertReason = 'REMINDER \u2014 STILL BELOW TARGET'; }\n  }\n}\n\nsd.lastPrice = currentPrice;\nif (shouldAlert) sd.lastAlertedAt = Date.now();\n\nreturn [{ json: {\n  hasData:              true,\n  belowTarget,\n  shouldAlert,\n  alertReason,\n  currentPrice,\n  lastPrice,\n  priceDiff,\n  priceTrend,\n  trendEmoji,\n  trendText,\n  historyLine,\n  calendarBlock,\n  cheapestDate:         cheapest.departureDate,\n  cheapestDateDisplay:  cheapest.displayDate,\n  airline:              cheapest.airline,\n  flightNumbers:        cheapest.flightNumbers,\n  depTime:              cheapest.depTime,\n  arrTime:              cheapest.arrTime,\n  duration:             cheapest.duration,\n  stopsLabel:           cheapest.stopsLabel,\n  layovers:             cheapest.layovers,\n  bookingLink:          cheapest.bookingLink,\n  TARGET,\n  checkedAt,\n  periodLabel,\n  datesChecked:         allDates.length,\n  datesWithData:        available.length\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "2a14a26e-a7d9-4a22-963f-46cf34e33aef",
      "name": "price drop Alert",
      "type": "n8n-nodes-base.slack",
      "position": [
        2512,
        240
      ],
      "parameters": {
        "text": "={{ \n'\ud83d\udea8\ud83d\udea8 *FLIGHT PRICE ALERT \u2014 PHL \u2708\ufe0f DAC* \ud83d\udea8\ud83d\udea8\\n'\n+ '\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n\\n'\n+ '\ud83d\udcb0 *BEST PRICE FOUND: $' + $json.currentPrice + ' USD*\\n'\n+ '\ud83d\udcc5 *Best Departure Date: ' + $json.cheapestDateDisplay + '*\\n'\n+ ($json.lastPrice !== null ? '\ud83d\udcb5 Previous best: $' + $json.lastPrice + ' USD\\n' : '')\n+ $json.trendEmoji + ' Price has *' + $json.trendText + '*\\n'\n+ '\ud83d\udcca Last 5 runs: ' + $json.historyLine + '\\n'\n+ '\ud83c\udfaf Your target: below $' + $json.TARGET + ' USD\\n\\n'\n+ '\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n'\n+ '\ud83d\udcc5 *FULL PRICE CALENDAR \u2014 Jul 1 to Jul 18*\\n'\n+ $json.calendarBlock + '\\n\\n'\n+ '\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n'\n+ '\u2708\ufe0f *CHEAPEST FLIGHT DETAILS*\\n'\n+ '\ud83c\udff7\ufe0f  Airline:    *' + $json.airline + '*\\n'\n+ '\ud83d\udd22  Flight No:  *' + $json.flightNumbers + '*\\n'\n+ '\ud83d\udccd  From:       *Philadelphia Intl Airport (PHL)*\\n'\n+ '\ud83d\udccd  To:         *Hazrat Shahjalal Intl Airport (DAC)*\\n'\n+ '\ud83d\udcc5  Date:       *' + $json.cheapestDateDisplay + '*\\n'\n+ '\ud83d\udeeb  Departure:  *' + $json.depTime + '*\\n'\n+ '\ud83d\udeec  Arrival:    *' + $json.arrTime + '*\\n'\n+ '\u23f1\ufe0f  Duration:   *' + $json.duration + '*\\n'\n+ '\ud83d\udd01  Stops:      *' + $json.stopsLabel + '*\\n'\n+ ($json.layovers ? '\ud83c\udfd9\ufe0f  Layovers:   *' + $json.layovers + '*\\n' : '')\n+ '\ud83d\udcb5  Price:      *$' + $json.currentPrice + ' USD*  |  Economy\\n\\n'\n+ '\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n'\n+ '\ud83c\udfab *<' + $json.bookingLink + '|\ud83d\udc49 BOOK THIS FLIGHT ON GOOGLE FLIGHTS NOW>*\\n\\n'\n+ '\u23f0 Checked: *' + $json.checkedAt + ' ET*\\n'\n+ '\ud83d\udce1 ' + $json.periodLabel + '\\n'\n+ '\ud83d\udccc Reason: ' + $json.alertReason + '\\n'\n+ '\ud83d\uddd3\ufe0f Dates scanned: ' + $json.datesChecked + ' | With data: ' + $json.datesWithData\n}}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0B4D54BBFB",
          "cachedResultName": "price-drop"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        },
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "839c6056-78ae-4c1b-b600-997d3cf1a89b",
      "name": "normal update",
      "type": "n8n-nodes-base.slack",
      "position": [
        2528,
        480
      ],
      "parameters": {
        "text": "={{ \n'\ud83d\udd0d *Flight Status \u2014 PHL \u2708\ufe0f DAC* | Google Flights | Economy\\n'\n+ '\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n\\n'\n+ '\ud83d\udcb5 *Lowest price across Jul 1\u201318: $' + $json.currentPrice + ' USD*\\n'\n+ '\ud83d\udcc5 *Cheapest date: ' + $json.cheapestDateDisplay + '*\\n'\n+ ($json.lastPrice !== null ? '\ud83d\udcb5 Previous best: $' + $json.lastPrice + ' USD\\n' : '')\n+ $json.trendEmoji + ' Price *' + $json.trendText + '*\\n'\n+ '\ud83d\udcca Last 5 runs: ' + $json.historyLine + '\\n'\n+ '\ud83c\udfaf Target: below $' + $json.TARGET + ' \u2014 *not reached yet*\\n\\n'\n+ '\ud83d\udcc5 *PRICE CALENDAR \u2014 Jul 1 to Jul 18*\\n'\n+ $json.calendarBlock + '\\n\\n'\n+ '\u2708\ufe0f *Cheapest Flight Right Now*\\n'\n+ '\ud83c\udff7\ufe0f ' + $json.airline + '   \ud83d\udd22 ' + $json.flightNumbers + '\\n'\n+ '\ud83d\udcc5 ' + $json.cheapestDateDisplay + '\\n'\n+ '\ud83d\udeeb ' + $json.depTime + '  \u2192  \ud83d\udeec ' + $json.arrTime + '\\n'\n+ '\u23f1\ufe0f ' + $json.duration + '   \ud83d\udd01 ' + $json.stopsLabel + '\\n'\n+ ($json.layovers ? '\ud83c\udfd9\ufe0f Layovers: ' + $json.layovers + '\\n' : '')\n+ '\\n'\n+ '\ud83d\udd17 *<' + $json.bookingLink + '|View on Google Flights>*\\n\\n'\n+ '\u23f0 Checked: *' + $json.checkedAt + ' ET*\\n'\n+ '\ud83d\udce1 ' + $json.periodLabel + '\\n'\n+ '\ud83d\uddd3\ufe0f Dates scanned: ' + $json.datesChecked + ' | With data: ' + $json.datesWithData\n}}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0B4B9XVD98",
          "cachedResultName": "regular-update"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        },
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "084ddd16-8915-4a25-bc2a-1b8943b3a77b",
      "name": "Main Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -304,
        -64
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 1004,
        "content": "## \u2708\ufe0f Flights Price Monitor \n\nThis workflow automatically tracks economy round-trip flight prices from different destinations across different departure dates , posting price updates and alerts to Slack whenever a deal is found or prices change.\n\n**Perfect for:** Travelers or deal-hunters who want hands-free flight price monitoring with instant Slack notifications.\n\n---\n\n## How it works\n\n1. **When clicking 'Execute workflow'** \u2014 Manually triggers the workflow for testing or on-demand checks.\n2. **Smart Schedule Gate** \u2014 Determines the current time zone and run mode: every 10 minutes at night, or once per hour during the day.\n3. **Should Run Now?** \u2014 Gates execution based on the schedule logic; stops the run if it is not the right time.\n4. **Generate dates** \u2014 Produces separate items, one for each departure date for given date.\n5. **Run an Actor and get dataset** \u2014 Calls the Apify Google Flights scraper once per date to fetch live pricing data from Google Flights for given destination.\n6. **Extract Cheapest for This Date** \u2014 Parses the Apify response, identifies the lowest-priced flight for that date, and extracts airline, flight numbers, times, duration, stops, and booking link.\n7. **Collect All  Date Results** \u2014 Aggregates all  individual date results into a single item for comparison.\n8. **Find Cheapest & Decide** \u2014 Compares all dates to find the overall cheapest, builds the full price calendar, computes the price trend against the last run, and determines whether to fire a price-drop alert.\n9. **Below given price ?** \u2014 Branches on whether the best price found is below the given price target threshold.\n10. **price drop Alert** \u2014 Sends an urgent Slack alert to the #price-drop channel when the price is below target.\n11. **normal update** \u2014 Sends a standard status update to the #regular-update channel when no threshold is breached.\n12. **all dates full report** \u2014 Sends a comprehensive 18-date price calendar report to the #sales-team channel on every run.\n\n---\n\n## Setup (~10 minutes)\n\n1. **Apify OAuth2 API** \u2014 Connect your Apify account and confirm access to the Google Flights scraper actor in the *Run an Actor and get dataset* node.\n2. **Slack OAuth2 API** \u2014 Authenticate your Slack workspace in the *price drop Alert*, *normal update*, and *all dates full report* nodes.\n3. **Slack channel IDs** \u2014 Verify the channel IDs for #price-drop, #regular-update, and #sales-team are correct in each respective Slack node.\n> This workflow uses Apify actor credits on every run across 18 dates. Monitor your Apify usage to avoid unexpected charges."
      },
      "typeVersion": 1
    },
    {
      "id": "bab7f189-0f8a-485a-a88a-bb5cbeda9798",
      "name": "Section 1: Trigger & Schedule",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        288,
        240
      ],
      "parameters": {
        "color": 5,
        "width": 760,
        "height": 380,
        "content": "## 1\ufe0f\u20e3 Trigger & Schedule Gate\n\nThe **When clicking 'Execute workflow'** node starts the process manually or on a schedule. The **Smart Schedule Gate** code node reads the current time in the America/New_York zone and decides whether the workflow is in nighttime mode (every 10 minutes) or daytime mode (once per hour). The **Should Run Now?** If node enforces this gate, stopping execution immediately if the timing conditions are not met."
      },
      "typeVersion": 1
    },
    {
      "id": "22556b5f-6c6e-41cc-979e-f8a674ab6b7e",
      "name": "Section 2: Date Generation & Scraping",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1072,
        240
      ],
      "parameters": {
        "color": 3,
        "width": 660,
        "height": 380,
        "content": "## 2\ufe0f\u20e3 Date Generation & Flight Scraping\n\nThe **Generate dates** code node produces 18 separate output items, one for each departure date from July 1 through July 18, 2026. For each date, **Run an Actor and get dataset** invokes the Apify Google Flights scraper with the PHL to DAC route and a 30-day return window, fetching live price data. The **Extract Cheapest for This Date** code node then parses the raw Apify response to pull out the lowest-priced flight option, its airline, flight numbers, departure and arrival times, duration, stop count, and a direct booking link."
      },
      "typeVersion": 1
    },
    {
      "id": "ae92ed27-d49b-4e4a-a7f0-f45447a77ae8",
      "name": "Section 3: Aggregation & Decision",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1760,
        240
      ],
      "parameters": {
        "color": 6,
        "width": 640,
        "height": 380,
        "content": "## 3\ufe0f\u20e3 Aggregation & Price Decision\n\nThe **Collect All 18 Date Results** aggregate node combines all 18 individual date items into a single payload. The **Find Cheapest & Decide** code node then compares every date to identify the overall cheapest flight, builds a full 18-date price calendar with \ud83d\udd25 and \ud83d\udc51 markers, computes the price trend against the previous run using persistent static data, and sets a flag for whether the price is below the $1,400 target. The **Below $1,400?** If node branches the flow depending on whether that threshold has been crossed."
      },
      "typeVersion": 1
    },
    {
      "id": "b923d914-ab0a-4d37-a238-e8b997619e4a",
      "name": "Section 4: Slack Notifications",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2416,
        16
      ],
      "parameters": {
        "color": 4,
        "width": 440,
        "height": 856,
        "content": "## 4\ufe0f\u20e3 Slack Notifications\n\nThree Slack nodes handle outbound messaging to different audiences. The **price drop Alert** node fires only when the price is below $1,400, sending a high-priority alert with full flight details to the #price-drop channel. The **normal update** node posts a standard status summary to the #regular-update channel when no threshold is breached. The **all dates full report** node sends a comprehensive 18-date price calendar to the #sales-team channel on every successful run, regardless of price level."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "cfd4c219-6407-4823-8c09-eb8cd6d5127e",
  "connections": {
    "Below $1,400?": {
      "main": [
        [
          {
            "node": "price drop Alert",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "normal update",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate dates": {
      "main": [
        [
          {
            "node": "Run an Actor and get dataset",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Should Run Now?": {
      "main": [
        [
          {
            "node": "Generate dates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Smart Schedule Gate": {
      "main": [
        [
          {
            "node": "Should Run Now?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Cheapest & Decide": {
      "main": [
        [
          {
            "node": "Below $1,400?",
            "type": "main",
            "index": 0
          },
          {
            "node": "all dates full report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Collect All 18 Date Results": {
      "main": [
        [
          {
            "node": "Find Cheapest & Decide",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run an Actor and get dataset": {
      "main": [
        [
          {
            "node": "Extract Cheapest for This Date",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Cheapest for This Date": {
      "main": [
        [
          {
            "node": "Collect All 18 Date Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking 'Execute workflow'": {
      "main": [
        [
          {
            "node": "Smart Schedule Gate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}