AutomationFlowsAI & RAG › Send Railway Queue Predictions with Openai Gpt-4o-mini, Gmail, and Google Sheets

Send Railway Queue Predictions with Openai Gpt-4o-mini, Gmail, and Google Sheets

ByisaWOW @isawow on n8n.io

This workflow collects train trip details via an n8n Form, predicts station crowding and queue times with a rule-based algorithm, generates a short advisory with OpenAI, then emails a color-coded HTML alert via Gmail and logs each prediction to Google Sheets. Receives a…

Event trigger★★★★☆ complexity11 nodesForm TriggerHTTP RequestGoogle SheetsGmail
AI & RAG Trigger: Event Nodes: 11 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #16836 — we link there as the canonical source.

This workflow follows the Form Trigger → 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": "sZRsdOerPYjPfRAJ",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Railway Station Queue Predictor \u2014 Form + Smart Wait Time + ChatGPT AI Advisory + Gmail + Sheets",
  "tags": [],
  "nodes": [
    {
      "id": "be16ddb5-255c-4d20-ac28-e8e73fb7f540",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1600,
        -80
      ],
      "parameters": {
        "color": 4,
        "width": 536,
        "height": 1372,
        "content": "## Railway Station Queue Predictor \u2014 Form + Smart Wait Time + ChatGPT AI Advisory + Gmail + Sheets\n\nFor Indian train travelers who want to know how early to reach the station and how to skip the worst queues. The traveler fills a short form with their train and station details. A built-in prediction algorithm (no external API) estimates crowd level, platform wait, ticket-counter queue, security time, and the ideal arrival time. A ChatGPT AI model then writes a friendly, plain-English advisory with station-specific tips. The traveler gets a colour-coded email alert, and every prediction is logged to Google Sheets.\n\n## What each node does\n- **1. Form \u2014 Traveler Train Details** collects train number, station, travel date and time, class, e-ticket status, and passenger count\n- **2. Code \u2014 Predict Queue Wait Times** runs the rule-based algorithm and returns crowd score, all wait times, alert level, and the recommended arrival time\n- **3. HTTP \u2014 AI Write Advisory** sends the prediction to a ChatGPT AI model and gets back a short traveler advisory\n- **4. Code \u2014 Parse Advisory and Alert Colors** merges the advisory and assigns the colour-coded alert level\n- **5. Code \u2014 Build HTML Email** builds the full colour-coded alert email\n- **6. Sheets \u2014 Log Prediction** appends the prediction to the log (parallel branch)\n- **7. Gmail \u2014 Send Queue Alert to Traveler** emails the alert to the traveler (parallel branch)\n\n## How the prediction works\nThe algorithm in node 2 scores expected crowding from day of week, time of day, festival season, station size, train type, and class of travel. Alert levels: GREEN (light), YELLOW (moderate), ORANGE (busy), RED (very crowded). It is a rule-based estimate, not live station data.\n\n## Google Sheet setup\nCreate a Google Sheet, add a tab named **Predictions**, and put these column headers in row 1:\nDate, Train Number, Train Name, Station, Travel Date, Departure Time, Class, Passengers, Has E-Ticket, Crowd Score, Alert Level, Platform Wait (min), Ticket Queue (min), Security Time (min), Total Buffer (min), Optimal Arrival, Traveler Email, Advisory Sent\n\n## Set up steps\n1. **OpenAI** \u2014 in **3. HTTP \u2014 AI Write Advisory**, connect your OpenAI API credential. A ChatGPT AI model is used for the advisory\n2. **Google Sheets** \u2014 in **6. Sheets \u2014 Log Prediction**, connect Google Sheets OAuth2 and replace `YOUR_QUEUE_LOG_SHEET_ID` with your sheet ID (from the sheet URL after /d/)\n3. **Gmail** \u2014 in **7. Gmail \u2014 Send Queue Alert to Traveler**, connect Gmail OAuth2. The recipient comes from the form\n4. Activate the workflow, open the Form URL, enter train details, and submit. The alert is emailed and logged instantly"
      },
      "typeVersion": 1
    },
    {
      "id": "e29779ae-ac73-4a1e-bc32-496fb658e411",
      "name": "Section \u2014 Form and Prediction Algorithm",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1008,
        48
      ],
      "parameters": {
        "color": 5,
        "width": 484,
        "height": 324,
        "content": "## Form and Prediction Algorithm\nTraveler fills train details. Code calculates crowd score, platform wait, ticket queue, security time, and the optimal arrival time \u2014 all without any external API."
      },
      "typeVersion": 1
    },
    {
      "id": "2fba56e1-5ac8-4d3d-994b-60065f1c73f7",
      "name": "Section \u2014 AI Advisory and Alert Level",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -512,
        48
      ],
      "parameters": {
        "color": 6,
        "width": 452,
        "height": 372,
        "content": "## AI Advisory and Alert Level\nThe ChatGPT AI model writes a friendly traveler advisory with station tips. Code parses the response and assigns the colour-coded alert level."
      },
      "typeVersion": 1
    },
    {
      "id": "2d161391-2d4f-4fa4-9962-63fc1151b407",
      "name": "Section \u2014 Build Email, Log, and Send",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        -144
      ],
      "parameters": {
        "color": 4,
        "width": 660,
        "height": 628,
        "content": "## Build Email, Log, and Send\nCode builds the colour-coded HTML email. Two parallel branches run: Sheets logs the prediction, and Gmail sends the alert to the traveler."
      },
      "typeVersion": 1
    },
    {
      "id": "0fb166ca-0a2d-4ba6-bd80-a53080bbfb2f",
      "name": "1. Form \u2014 Traveler Train Details",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        -928,
        192
      ],
      "parameters": {
        "options": {
          "path": "railway-queue-predictor",
          "buttonLabel": "Predict My Queue Times",
          "appendAttribution": false
        },
        "formTitle": "Railway Queue Predictor",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Your Name",
              "placeholder": "e.g. Rahul Kumar",
              "requiredField": true
            },
            {
              "fieldLabel": "Your Email",
              "placeholder": "your@email.com \u2014 advisory will be sent here",
              "requiredField": true
            },
            {
              "fieldLabel": "Train Number",
              "placeholder": "e.g. 12301 or 22691",
              "requiredField": true
            },
            {
              "fieldLabel": "Train Name",
              "placeholder": "e.g. Rajdhani Express, Shatabdi, Duronto, Express",
              "requiredField": true
            },
            {
              "fieldLabel": "Departure Station Code",
              "placeholder": "e.g. NDLS, CSMT, HWH, MAS, SBC, ADI, PUNE, LKO",
              "requiredField": true
            },
            {
              "fieldLabel": "Travel Date",
              "placeholder": "YYYY-MM-DD e.g. 2025-07-15",
              "requiredField": true
            },
            {
              "fieldLabel": "Train Departure Time",
              "placeholder": "HH:MM in 24hr format e.g. 06:30 or 17:45",
              "requiredField": true
            },
            {
              "fieldType": "dropdown",
              "fieldLabel": "Class of Travel",
              "fieldOptions": {
                "values": [
                  {
                    "option": "SL \u2014 Sleeper Class"
                  },
                  {
                    "option": "3A \u2014 AC 3 Tier"
                  },
                  {
                    "option": "2A \u2014 AC 2 Tier"
                  },
                  {
                    "option": "1A \u2014 AC First Class"
                  },
                  {
                    "option": "CC \u2014 Chair Car"
                  },
                  {
                    "option": "2S \u2014 Second Sitting"
                  }
                ]
              },
              "requiredField": true
            },
            {
              "fieldType": "dropdown",
              "fieldLabel": "Do you have IRCTC e-ticket on app?",
              "fieldOptions": {
                "values": [
                  {
                    "option": "Yes \u2014 I have digital ticket on IRCTC app"
                  },
                  {
                    "option": "No \u2014 I need to collect physical ticket or PRS printout"
                  }
                ]
              },
              "requiredField": true
            },
            {
              "fieldLabel": "Number of Passengers",
              "placeholder": "e.g. 1, 2, 4",
              "requiredField": true
            }
          ]
        },
        "formDescription": "Enter your train details to get a predicted crowd and wait time report for your departure station. You will receive a personalized advisory email with tips to avoid queues."
      },
      "typeVersion": 2.2
    },
    {
      "id": "ed593746-0cf1-47df-8bcb-f46871f38d3f",
      "name": "2. Code \u2014 Predict Queue Wait Times",
      "type": "n8n-nodes-base.code",
      "position": [
        -688,
        192
      ],
      "parameters": {
        "jsCode": "// RAILWAY STATION QUEUE PREDICTION ALGORITHM\n// Predicts crowd levels and wait times based on multiple factors\n// No external API needed \u2014 rule-based scoring system\n\nconst row = $json;\n\n// --- Extract form inputs ---\nconst travelerName  = (row['Your Name']              || '').trim();\nconst email         = (row['Your Email']             || '').trim();\nconst trainNo       = (row['Train Number']           || '').trim();\nconst trainName     = (row['Train Name']             || '').trim();\nconst stationCode   = (row['Departure Station Code'] || '').trim().toUpperCase();\nconst travelDate    = (row['Travel Date']            || '').trim();\nconst departureTime = (row['Train Departure Time']   || '').trim();\nconst travelClass   = (row['Class of Travel']        || '').trim();\nconst hasETicket    = (row['Do you have IRCTC e-ticket on app?'] || '').startsWith('Yes');\nconst passengers    = parseInt(row['Number of Passengers']) || 1;\n\nif (!email || !trainNo || !stationCode || !travelDate || !departureTime) {\n  throw new Error('Train number, station code, travel date, departure time, and email are all required.');\n}\n\n// --- Parse travel date and time ---\nconst travelDateObj = new Date(travelDate);\nconst dayOfWeek     = travelDateObj.getDay(); // 0=Sun, 6=Sat\nconst month         = travelDateObj.getMonth() + 1; // 1-12\nconst [depHour, depMin] = departureTime.split(':').map(Number);\nconst depMinutes    = (depHour * 60) + (depMin || 0);\n\n// --- FACTOR 1: Day of week multiplier ---\nconst dayMultiplier =\n  dayOfWeek === 0 ? 1.35 :\n  dayOfWeek === 1 ? 1.25 :\n  dayOfWeek === 5 ? 1.20 :\n  dayOfWeek === 6 ? 1.30 :\n  1.0;\n\n// --- FACTOR 2: Time of day multiplier ---\nconst timeMultiplier =\n  (depMinutes >= 300 && depMinutes < 540)  ? 1.35 :\n  (depMinutes >= 960 && depMinutes < 1260) ? 1.30 :\n  (depMinutes >= 1260 || depMinutes < 300) ? 1.15 :\n  1.0;\n\n// --- FACTOR 3: Season/festival multiplier ---\nconst seasonMultiplier =\n  (month === 10 || month === 11) ? 1.40 :\n  (month === 12 || month === 1)  ? 1.25 :\n  (month === 5  || month === 6)  ? 1.30 :\n  month === 3                    ? 1.20 :\n  1.0;\n\n// --- FACTOR 4: Station size multiplier ---\nconst stationMultipliers = {\n  'NDLS': 1.50, 'DLI':  1.40, 'CSMT': 1.45, 'BCT':  1.35,\n  'HWH':  1.40, 'SDAH': 1.25, 'MAS':  1.35, 'SBC':  1.30,\n  'ADI':  1.30, 'PUNE': 1.25, 'LKO':  1.30, 'PNBE': 1.35,\n  'JP':   1.25, 'HYB':  1.30, 'SC':   1.25\n};\nconst stationMult = stationMultipliers[stationCode] || 1.15;\n\n// --- FACTOR 5: Train type multiplier ---\nconst trainNameLower = trainName.toLowerCase();\nconst trainMultiplier =\n  (trainNameLower.includes('rajdhani') || trainNameLower.includes('shatabdi') || trainNameLower.includes('tejas') || trainNameLower.includes('vande')) ? 0.75 :\n  (trainNameLower.includes('duronto') || trainNameLower.includes('gatimaan')) ? 0.80 :\n  (trainNameLower.includes('superfast') || trainNameLower.includes('sf')) ? 1.05 :\n  (trainNameLower.includes('express')) ? 1.10 :\n  1.20;\n\n// --- FACTOR 6: Class multiplier ---\nconst classCode       = travelClass.split('\u2014')[0].trim();\nconst classMultiplier =\n  classCode === 'SL' ? 1.40 :\n  classCode === '2S' ? 1.30 :\n  classCode === 'CC' ? 1.10 :\n  classCode === '3A' ? 0.90 :\n  classCode === '2A' ? 0.80 :\n  classCode === '1A' ? 0.70 :\n  1.0;\n\n// --- COMBINED CROWD SCORE ---\nconst baseCrowdScore = 50;\nconst rawScore = baseCrowdScore * dayMultiplier * timeMultiplier * seasonMultiplier * stationMult * trainMultiplier * classMultiplier;\nconst crowdScore = Math.min(Math.round(rawScore), 100);\n\n// --- WAIT TIME CALCULATIONS (in minutes) ---\nconst platformWait = Math.round((crowdScore / 100) * 25 + 5);\nconst ticketQueue = hasETicket ? 0 : Math.round((crowdScore / 100) * 35 + 5);\nconst hasSecurityCheck = ['NDLS', 'DLI', 'CSMT', 'HWH', 'MAS', 'SBC'].includes(stationCode);\nconst securityTime = hasSecurityCheck ? Math.round((crowdScore / 100) * 20 + 5) : 0;\nconst platformWalk = stationMult > 1.35 ? 12 : stationMult > 1.25 ? 8 : 5;\nconst totalBuffer = platformWait + ticketQueue + securityTime + platformWalk + (passengers > 3 ? 10 : 0);\n\n// --- Optimal arrival time ---\nconst optimalArrivalMinutes = depMinutes - totalBuffer;\nconst optHour = Math.floor(((optimalArrivalMinutes % 1440) + 1440) % 1440 / 60);\nconst optMin  = Math.floor(((optimalArrivalMinutes % 1440) + 1440) % 1440 % 60);\nconst optimalArrival = String(optHour).padStart(2,'0') + ':' + String(optMin).padStart(2,'0');\n\n// --- ALERT LEVEL ---\nconst alertLevel =\n  crowdScore >= 80 ? 'RED' :\n  crowdScore >= 60 ? 'ORANGE' :\n  crowdScore >= 40 ? 'YELLOW' :\n  'GREEN';\n\n// --- BUILD CONTEXT FOR AI ---\nconst predictionContext = [\n  'Traveler: ' + travelerName,\n  'Train: ' + trainNo + ' ' + trainName,\n  'Station: ' + stationCode,\n  'Travel Date: ' + travelDate,\n  'Day of Week: ' + ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'][dayOfWeek],\n  'Departure Time: ' + departureTime,\n  'Class: ' + travelClass,\n  'Passengers: ' + passengers,\n  'Has e-Ticket: ' + (hasETicket ? 'Yes' : 'No'),\n  '',\n  'PREDICTION RESULTS:',\n  'Crowd Score: ' + crowdScore + '/100',\n  'Alert Level: ' + alertLevel,\n  'Platform Wait: ' + platformWait + ' min',\n  'Ticket Counter Queue: ' + (hasETicket ? 'Skipped (e-ticket)' : ticketQueue + ' min'),\n  'Security Check: ' + (hasSecurityCheck ? securityTime + ' min' : 'Not required at this station'),\n  'Platform Walk: ' + platformWalk + ' min',\n  'Total Buffer Needed: ' + totalBuffer + ' minutes',\n  'Recommended Arrival: ' + optimalArrival + ' (arrive by this time)',\n  '',\n  'Factors driving this prediction:',\n  'Day multiplier: ' + dayMultiplier + 'x (' + ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][dayOfWeek] + ')',\n  'Time of day: ' + timeMultiplier + 'x (departure at ' + departureTime + ')',\n  'Season: ' + seasonMultiplier + 'x (month ' + month + ')',\n  'Station size: ' + stationMult + 'x (' + stationCode + ')',\n  'Train type: ' + trainMultiplier + 'x (' + trainName + ')',\n  'Class: ' + classMultiplier + 'x (' + classCode + ')'\n].join('\\n');\n\nconst loggedAt = new Date().toISOString();\n\nreturn [{\n  json: {\n    travelerName, email, trainNo, trainName, stationCode,\n    travelDate, departureTime, travelClass, classCode,\n    hasETicket, passengers, dayOfWeek,\n    crowdScore, alertLevel,\n    platformWait, ticketQueue, securityTime,\n    platformWalk, totalBuffer, optimalArrival,\n    hasSecurityCheck,\n    predictionContext,\n    loggedAt\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "9d90dfa5-5125-4296-91c5-5ebec9534f39",
      "name": "3. HTTP \u2014 AI Write Advisory",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -448,
        192
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/chat/completions",
        "method": "POST",
        "options": {
          "timeout": 30000,
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "jsonBody": "={\n  \"model\": \"gpt-4o-mini\",\n  \"temperature\": 0.5,\n  \"max_tokens\": 800,\n  \"messages\": [\n    {\n      \"role\": \"system\",\n      \"content\": \"You are a helpful railway travel assistant for Indian train travelers. Write a friendly, practical travel advisory in simple English. Be specific, actionable, and conversational. Do not use markdown formatting \u2014 plain text paragraphs only.\"\n    },\n    {\n      \"role\": \"user\",\n      \"content\": {{ JSON.stringify(\n        \"Write a short traveler advisory based on this queue prediction.\\n\\n\" + $json.predictionContext +\n        \"\\n\\nInclude these points in your advisory (use plain text, no markdown, no bullets, no headers):\\n\" +\n        \"1. One opening sentence mentioning their train and the crowd level prediction.\\n\" +\n        \"2. A specific recommendation on what time to arrive and why.\\n\" +\n        \"3. One practical tip for their class of travel (\" + $json.classCode + \") at \" + $json.stationCode + \" station to save time.\\n\" +\n        \"4. One tip about the ticket counter situation based on whether they have an e-ticket or not.\\n\" +\n        \"5. One general safety or comfort tip for travelling with \" + $json.passengers + \" passengers.\\n\" +\n        \"Keep the entire advisory under 120 words. Be warm and helpful.\"\n      ) }}\"\n    }\n  ]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "openAiApi"
      },
      "typeVersion": 4.2
    },
    {
      "id": "c10c5399-6ff7-4deb-9141-7593eefacb2a",
      "name": "4. Code \u2014 Parse Advisory and Alert Colors",
      "type": "n8n-nodes-base.code",
      "position": [
        -208,
        192
      ],
      "parameters": {
        "jsCode": "// Extract AI advisory text and merge with prediction data\nconst resp       = $input.first().json;\nconst prediction = $('2. Code \u2014 Predict Queue Wait Times').first().json;\n\nconst advisory = resp?.choices?.[0]?.message?.content || 'Please arrive at least ' + prediction.totalBuffer + ' minutes before your train departure. Check the station board for your platform number.';\n\n// Alert level colors for email\nconst alertColor =\n  prediction.alertLevel === 'RED'    ? '#e74c3c' :\n  prediction.alertLevel === 'ORANGE' ? '#e67e22' :\n  prediction.alertLevel === 'YELLOW' ? '#f39c12' :\n  '#27ae60';\n\nconst alertBg =\n  prediction.alertLevel === 'RED'    ? '#fdf0f0' :\n  prediction.alertLevel === 'ORANGE' ? '#fdf5ec' :\n  prediction.alertLevel === 'YELLOW' ? '#fefde7' :\n  '#f0fdf4';\n\nconst alertText =\n  prediction.alertLevel === 'RED'    ? 'Very Crowded \u2014 Arrive 90+ minutes early' :\n  prediction.alertLevel === 'ORANGE' ? 'Busy Station \u2014 Arrive 60-90 minutes early' :\n  prediction.alertLevel === 'YELLOW' ? 'Moderate Crowd \u2014 Arrive 45-60 minutes early' :\n  'Light Crowd \u2014 Arrive 30 minutes early is sufficient';\n\nreturn [{\n  json: {\n    ...prediction,\n    advisory: advisory.trim(),\n    alertColor,\n    alertBg,\n    alertText\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "8185aa7b-1c5a-4e1c-9db0-4a0ee7bd73bc",
      "name": "5. Code \u2014 Build HTML Email",
      "type": "n8n-nodes-base.code",
      "position": [
        48,
        192
      ],
      "parameters": {
        "jsCode": "// Build the complete HTML email for the traveler\nconst d = $input.first().json;\n\nconst fmtMin = (m) => m === 0 ? 'Skipped' : m + ' min';\n\nconst htmlEmail =\n  '<!DOCTYPE html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"></head>'\n  + '<body style=\"font-family:Arial,sans-serif;max-width:640px;margin:0 auto;background:#f5f5f5;\">'\n\n  + '<div style=\"background:#1a237e;padding:24px 28px;border-radius:8px 8px 0 0;\">'\n  + '<h1 style=\"color:#fff;font-size:19px;margin:0;\">Railway Queue Predictor</h1>'\n  + '<p style=\"color:#c5cae9;font-size:13px;margin:6px 0 0;\">' + d.travelerName + ' \u2014 Train ' + d.trainNo + ' ' + d.trainName + '</p>'\n  + '</div>'\n\n  + '<div style=\"background:' + d.alertColor + ';padding:16px 24px;\">'\n  + '<div style=\"color:#fff;font-size:16px;font-weight:700;\">' + d.alertLevel + ' \u2014 ' + d.alertText + '</div>'\n  + '<div style=\"color:rgba(255,255,255,0.9);font-size:13px;margin-top:2px;\">Crowd Score: ' + d.crowdScore + '/100</div>'\n  + '</div>'\n\n  + '<div style=\"background:#283593;padding:10px 24px;display:flex;gap:20px;flex-wrap:wrap;\">'\n  + '<span style=\"color:#c5cae9;font-size:13px;\">' + d.travelDate + '</span>'\n  + '<span style=\"color:#c5cae9;font-size:13px;\">' + d.stationCode + '</span>'\n  + '<span style=\"color:#c5cae9;font-size:13px;\">Departs ' + d.departureTime + '</span>'\n  + '<span style=\"color:#c5cae9;font-size:13px;\">' + d.classCode + '</span>'\n  + '<span style=\"color:#c5cae9;font-size:13px;\">' + d.passengers + ' passenger(s)</span>'\n  + '</div>'\n\n  + '<div style=\"background:#fff;padding:20px 24px;\">'\n\n  + '<div style=\"background:' + d.alertBg + ';border:2px solid ' + d.alertColor + ';border-radius:8px;padding:16px 20px;text-align:center;margin-bottom:20px;\">'\n  + '<div style=\"font-size:12px;color:#666;text-transform:uppercase;letter-spacing:1px;margin-bottom:4px;\">Recommended Arrival Time</div>'\n  + '<div style=\"font-size:32px;font-weight:700;color:' + d.alertColor + ';\">' + d.optimalArrival + '</div>'\n  + '<div style=\"font-size:13px;color:#555;margin-top:4px;\">Arrive by this time \u2014 total buffer needed: <strong>' + d.totalBuffer + ' minutes</strong></div>'\n  + '</div>'\n\n  + '<h2 style=\"font-size:15px;color:#1a237e;border-bottom:2px solid #eee;padding-bottom:8px;margin:0 0 14px;\">Queue Time Breakdown</h2>'\n  + '<table style=\"width:100%;border-collapse:collapse;font-size:14px;margin-bottom:20px;\">'\n  + '<tr style=\"background:#e8eaf6;\"><th style=\"padding:9px 12px;text-align:left;font-size:12px;color:#3f51b5;\">Queue Type</th><th style=\"padding:9px 12px;text-align:right;font-size:12px;color:#3f51b5;\">Predicted Wait</th><th style=\"padding:9px 12px;text-align:right;font-size:12px;color:#3f51b5;\">Status</th></tr>'\n  + '<tr><td style=\"padding:9px 12px;border-bottom:1px solid #f0f0f0;\">Platform Walk</td><td style=\"padding:9px 12px;text-align:right;border-bottom:1px solid #f0f0f0;\">' + d.platformWalk + ' min</td><td style=\"padding:9px 12px;text-align:right;border-bottom:1px solid #f0f0f0;font-size:12px;color:#666;\">Always required</td></tr>'\n  + '<tr><td style=\"padding:9px 12px;border-bottom:1px solid #f0f0f0;\">Security Check</td><td style=\"padding:9px 12px;text-align:right;border-bottom:1px solid #f0f0f0;\">' + fmtMin(d.securityTime) + '</td><td style=\"padding:9px 12px;text-align:right;border-bottom:1px solid #f0f0f0;font-size:12px;color:#666;\">' + (d.hasSecurityCheck ? 'Required at ' + d.stationCode : 'Not required') + '</td></tr>'\n  + '<tr><td style=\"padding:9px 12px;border-bottom:1px solid #f0f0f0;\">Ticket Counter</td><td style=\"padding:9px 12px;text-align:right;border-bottom:1px solid #f0f0f0;\">' + fmtMin(d.ticketQueue) + '</td><td style=\"padding:9px 12px;text-align:right;border-bottom:1px solid #f0f0f0;font-size:12px;color:' + (d.hasETicket ? '#27ae60' : '#e74c3c') + ';\">' + (d.hasETicket ? 'E-ticket \u2014 skip queue' : 'Physical ticket needed') + '</td></tr>'\n  + '<tr><td style=\"padding:9px 12px;border-bottom:1px solid #f0f0f0;\">Platform Wait</td><td style=\"padding:9px 12px;text-align:right;border-bottom:1px solid #f0f0f0;\">' + d.platformWait + ' min</td><td style=\"padding:9px 12px;text-align:right;border-bottom:1px solid #f0f0f0;font-size:12px;color:#666;\">Find seat, settle luggage</td></tr>'\n  + '<tr style=\"background:#f0f4ff;\"><td style=\"padding:9px 12px;font-weight:700;color:#1a237e;\">Total Buffer</td><td style=\"padding:9px 12px;text-align:right;font-weight:700;color:' + d.alertColor + ';\">' + d.totalBuffer + ' min</td><td style=\"padding:9px 12px;text-align:right;font-size:12px;color:' + d.alertColor + ';font-weight:600;\">' + d.alertLevel + '</td></tr>'\n  + '</table>'\n\n  + '<div style=\"background:#e8eaf6;border-left:4px solid #3f51b5;padding:14px;border-radius:0 6px 6px 0;margin-bottom:16px;\">'\n  + '<h2 style=\"margin:0 0 8px;font-size:14px;color:#1a237e;\">Travel Advisory</h2>'\n  + '<p style=\"margin:0;font-size:14px;color:#333;line-height:1.7;\">' + d.advisory + '</p>'\n  + '</div>'\n\n  + '<div style=\"background:#e8f5e9;border-left:4px solid #43a047;padding:12px 14px;border-radius:0 6px 6px 0;\">'\n  + '<h2 style=\"margin:0 0 8px;font-size:13px;color:#1b5e20;\">Quick Tips</h2>'\n  + '<p style=\"margin:0 0 4px;font-size:13px;color:#444;\">- Keep your ticket and ID ready before entering the station</p>'\n  + '<p style=\"margin:0 0 4px;font-size:13px;color:#444;\">- Check the NTES app for real-time train location and platform number</p>'\n  + '<p style=\"margin:0 0 4px;font-size:13px;color:#444;\">- Platform number is usually announced 20-30 minutes before departure</p>'\n  + (d.hasETicket ? '<p style=\"margin:0;font-size:13px;color:#27ae60;font-weight:600;\">- You have an e-ticket \u2014 go straight to your platform and skip the counter</p>' : '<p style=\"margin:0;font-size:13px;color:#e74c3c;font-weight:600;\">- You need a physical ticket \u2014 reach early to avoid the counter queue</p>')\n  + '</div>'\n\n  + '</div>'\n\n  + '<div style=\"background:#1a237e;padding:12px 24px;text-align:center;border-radius:0 0 8px 8px;\">'\n  + '<p style=\"color:#c5cae9;font-size:11px;margin:0;\">Railway Queue Predictor \u2014 n8n + ChatGPT AI</p>'\n  + '<p style=\"color:#7986cb;font-size:11px;margin:4px 0 0;\">Prediction based on general patterns. Always allow extra time for unexpected delays.</p>'\n  + '</div>'\n  + '</body></html>';\n\nreturn [{\n  json: {\n    ...d,\n    htmlEmail,\n    emailSubject: d.alertLevel + ' Queue Alert \u2014 Train ' + d.trainNo + ' | Arrive by ' + d.optimalArrival + ' | ' + d.stationCode\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "bc2d4ac8-8376-466d-b475-b174fad1e24d",
      "name": "6. Sheets \u2014 Log Prediction",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        288,
        32
      ],
      "parameters": {
        "columns": {
          "value": {
            "Date": "={{ $json.loggedAt.split('T')[0] }}",
            "Class": "={{ $json.classCode }}",
            "Station": "={{ $json.stationCode }}",
            "Passengers": "={{ $json.passengers }}",
            "Train Name": "={{ $json.trainName }}",
            "Alert Level": "={{ $json.alertLevel }}",
            "Crowd Score": "={{ $json.crowdScore }}",
            "Travel Date": "={{ $json.travelDate }}",
            "Has E-Ticket": "={{ $json.hasETicket ? 'Yes' : 'No' }}",
            "Train Number": "={{ $json.trainNo }}",
            "Advisory Sent": "Yes",
            "Departure Time": "={{ $json.departureTime }}",
            "Traveler Email": "={{ $json.email }}",
            "Optimal Arrival": "={{ $json.optimalArrival }}",
            "Ticket Queue (min)": "={{ $json.ticketQueue }}",
            "Total Buffer (min)": "={{ $json.totalBuffer }}",
            "Platform Wait (min)": "={{ $json.platformWait }}",
            "Security Time (min)": "={{ $json.securityTime }}"
          },
          "schema": [
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Train Number",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Train Number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Train Name",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Train Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Station",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Station",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Travel Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Travel Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Departure Time",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Departure Time",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Class",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Class",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Passengers",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Passengers",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Has E-Ticket",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Has E-Ticket",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Crowd Score",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Crowd Score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Alert Level",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Alert Level",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Platform Wait (min)",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Platform Wait (min)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Ticket Queue (min)",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Ticket Queue (min)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Security Time (min)",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Security Time (min)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Total Buffer (min)",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Total Buffer (min)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Optimal Arrival",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Optimal Arrival",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Traveler Email",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Traveler Email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Advisory Sent",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Advisory Sent",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Predictions"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_QUEUE_LOG_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "5c57ee84-93ca-4eb1-81e8-bec30f82408d",
      "name": "7. Gmail \u2014 Send Queue Alert to Traveler",
      "type": "n8n-nodes-base.gmail",
      "position": [
        288,
        224
      ],
      "parameters": {
        "sendTo": "={{ $json.email }}",
        "message": "={{ $json.htmlEmail }}",
        "options": {
          "senderName": "Railway Queue Predictor"
        },
        "subject": "={{ $json.emailSubject }}"
      },
      "typeVersion": 2.1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "495ddbc1-3420-4ce4-972f-c4f44c63a72f",
  "nodeGroups": [],
  "connections": {
    "5. Code \u2014 Build HTML Email": {
      "main": [
        [
          {
            "node": "6. Sheets \u2014 Log Prediction",
            "type": "main",
            "index": 0
          },
          {
            "node": "7. Gmail \u2014 Send Queue Alert to Traveler",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. HTTP \u2014 AI Write Advisory": {
      "main": [
        [
          {
            "node": "4. Code \u2014 Parse Advisory and Alert Colors",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. Form \u2014 Traveler Train Details": {
      "main": [
        [
          {
            "node": "2. Code \u2014 Predict Queue Wait Times",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Code \u2014 Predict Queue Wait Times": {
      "main": [
        [
          {
            "node": "3. HTTP \u2014 AI Write Advisory",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Code \u2014 Parse Advisory and Alert Colors": {
      "main": [
        [
          {
            "node": "5. Code \u2014 Build HTML Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

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

About this workflow

This workflow collects train trip details via an n8n Form, predicts station crowding and queue times with a rule-based algorithm, generates a short advisory with OpenAI, then emails a color-coded HTML alert via Gmail and logs each prediction to Google Sheets. Receives a…

Source: https://n8n.io/workflows/16836/ — 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

This workflow hosts an n8n intake form, sends submissions to Anthropic Claude for practice-area classification and qualification, then routes results to Gmail and Google Sheets before returning a conf

Form Trigger, HTTP Request, Gmail +2
AI & RAG

This workflow collects SIP and mutual fund details via an n8n Form, calculates goal and performance metrics, generates a personalized advisory using OpenAI, emails a color-coded HTML report via Gmail,

Form Trigger, HTTP Request, Google Sheets +1
AI & RAG

The competitive edge, delivered. This Customer Intelligence Engine simultaneously analyzes the web, Reddit, and X/Twitter to generate a professional, actionable executive briefing.

Reddit, N8N Nodes Serpapi, HTTP Request +4
AI & RAG

Generate three recipes using AI for quick meal options. Uses formTrigger, googleGemini, httpRequest, googleSheets. Event-driven trigger; 18 nodes.

Form Trigger, Google Gemini, HTTP Request +2
AI & RAG

Client Registration. Uses httpRequest, openAi, formTrigger, googleSheets. Event-driven trigger; 9 nodes.

HTTP Request, OpenAI, Form Trigger +2