This workflow corresponds to n8n.io template #16185 — we link there as the canonical source.
This workflow follows the Agent → HTTP Request recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "Aa91A2pT4DEUZQ6i",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Predict airport queue wait times and send traveler alerts via email",
"tags": [],
"nodes": [
{
"id": "4bbe129d-3600-44aa-ab2f-f14c95642267",
"name": "Sticky Note - Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-288,
-64
],
"parameters": {
"width": 960,
"height": 948,
"content": "## \u2708\ufe0f Airport Queue & Wait-Time Predictor\n\nThis workflow predicts real-time wait times for security, immigration, and boarding queues using live airport data and historical patterns. It recommends optimal arrival times and sends proactive alerts to minimize traveler delays.\n\n### Who's it for\n\u2022 Travelers wanting smart arrival-time guidance\n\u2022 Airport ops teams monitoring queue health\n\u2022 Travel apps needing real-time queue APIs\n\u2022 Airlines proactively alerting at-risk passengers\n\n### How it works\n1. Triggered by traveler request (webhook) or scheduled poll (every 5 min)\n2. Fetches live airport operational data (queues, staffing, flight status)\n3. Wait \u2014 rate-limit buffer before heavy computation\n4. JS engine runs wait-time prediction model + arrival recommendation\n5. AI node generates natural-language traveler alert message\n6. Wait \u2014 review / de-duplication buffer\n7. Sends push notification / SMS alert to traveler\n8. Logs prediction + outcome to Google Sheets tracker\n\n### Setup\n1. Import workflow into n8n\n2. Configure credentials: AviationStack or AeroDataBox API, SendGrid / Twilio, Google Sheets OAuth2\n3. Set YOUR_SHEET_ID in tracker node\n4. Set ALERT_EMAIL / ALERT_PHONE in notification nodes\n5. Activate workflow\n\n### Requirements\n\u2022 Airport data API (AviationStack / AeroDataBox / FlightAware)\n\u2022 SendGrid (email) or Twilio (SMS) for alerts\n\u2022 Google Sheets OAuth2\n\u2022 OpenAI / Anthropic API key\n\n### Customisation\n\u2022 Tune threshold minutes in JS Prediction node\n\u2022 Add extra checkpoints (lounge, gate bus) in Set node\n\u2022 Swap AI model in LLM node\n\u2022 Extend Google Sheet columns for analytics"
},
"typeVersion": 1
},
{
"id": "a732f4aa-d2a5-46b3-b669-ccaaaa7d7089",
"name": "Sticky Note - Stage 1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1056,
80
],
"parameters": {
"color": 6,
"width": 780,
"height": 840,
"content": "## 1. Trigger & Data Ingestion\n\nTwo entry points:\n\u2022 Webhook \u2192 traveler submits flight details in real time\n\u2022 Scheduler \u2192 polls airport data every 5 minutes for proactive alerts\n\nSet node normalises both payloads into a unified context object before downstream processing."
},
"typeVersion": 1
},
{
"id": "3ba21e52-dedc-4837-a134-12d888ecdcf8",
"name": "Sticky Note - Stage 2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1888,
16
],
"parameters": {
"color": 6,
"width": 740,
"height": 908,
"content": "## 2. Prediction & AI Recommendation\n\n\u2022 Wait 1 throttles API calls before fetching live queue data\n\u2022 JS Code node runs the full prediction model:\n \u2013 Historical baseline lookup (hour-of-day \u00d7 day-of-week matrix)\n \u2013 Live queue multiplier from ingested data\n \u2013 Seasonal adjustment factor\n \u2013 Optimal arrival window calculation\n \u2013 Risk flag: willMissFlight boolean\n\u2022 AI Agent node converts raw prediction into a friendly, actionable traveler message"
},
"typeVersion": 1
},
{
"id": "a42ba0c0-b8ce-421c-b4f7-e2427a8667fb",
"name": "Sticky Note - Stage 3",
"type": "n8n-nodes-base.stickyNote",
"position": [
2672,
224
],
"parameters": {
"color": 6,
"width": 980,
"height": 580,
"content": "## 3. Alert Delivery & Tracking\n\n\u2022 HTTP node fires SMS via Twilio OR email via SendGrid\n\u2022 HTTP node appends prediction row to Google Sheets for analytics\n\u2022 Both outputs run in parallel after Wait 2"
},
"typeVersion": 1
},
{
"id": "c76c6fc9-b5e4-4cd8-a50e-fabca6dfe509",
"name": "Webhook - Traveler Queue Request",
"type": "n8n-nodes-base.webhook",
"position": [
1232,
384
],
"parameters": {
"path": "airport-queue-check",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 1.1
},
{
"id": "f064b52c-af1f-4ead-84f3-9b7e8588278f",
"name": "Poll Airport Data Every 5 Min",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
1232,
592
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "*/5 * * * *"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "858bdd5a-09e8-4b98-801c-049df33e13f4",
"name": "Normalise Traveler & Airport Context",
"type": "n8n-nodes-base.set",
"position": [
1488,
480
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"name": "flightNumber",
"type": "string",
"value": "={{ $json.flightNumber || $json.body?.flightNumber || 'UNKNOWN' }}"
},
{
"name": "airportCode",
"type": "string",
"value": "={{ $json.airportCode || $json.body?.airportCode || 'DEL' }}"
},
{
"name": "terminal",
"type": "string",
"value": "={{ $json.terminal || $json.body?.terminal || 'T2' }}"
},
{
"name": "departureTime",
"type": "string",
"value": "={{ $json.departureTime || $json.body?.departureTime || new Date(Date.now() + 3 * 60 * 60 * 1000).toISOString() }}"
},
{
"name": "travelerEmail",
"type": "string",
"value": "={{ $json.travelerEmail || $json.body?.travelerEmail || '' }}"
},
{
"name": "travelerPhone",
"type": "string",
"value": "={{ $json.travelerPhone || $json.body?.travelerPhone || '' }}"
},
{
"name": "passengerType",
"type": "string",
"value": "={{ $json.passengerType || $json.body?.passengerType || 'standard' }}"
},
{
"name": "requestId",
"type": "string",
"value": "={{ $json.requestId || 'REQ-' + Date.now().toString() }}"
},
{
"name": "liveQueueData",
"type": "object",
"value": "={{ $json.liveQueueData || $json.body?.liveQueueData || { security: { queueLength: 45, staffCount: 6, avgProcessTimeSec: 38 }, immigration: { queueLength: 30, staffCount: 4, avgProcessTimeSec: 55 }, boarding: { queueLength: 20, gateOpen: true, boardingStartsMin: 40 } } }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "1c3ca75e-1c55-49de-a111-d2eb70bf793e",
"name": "Wait 1 - API Rate Limit Buffer",
"type": "n8n-nodes-base.wait",
"position": [
1728,
480
],
"parameters": {},
"typeVersion": 1
},
{
"id": "a134d45d-9876-420a-a62f-dca0b31e041a",
"name": "JS - Wait-Time Prediction Engine",
"type": "n8n-nodes-base.code",
"position": [
2016,
480
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// ============================================================\n// AIRPORT QUEUE WAIT-TIME PREDICTION ENGINE\n// ============================================================\nconst item = $input.item.json;\n\n// --- 1. Historical baseline matrix (minutes) per checkpoint\n// Rows = hour of day (0-23), values = [security, immigration, boarding]\nconst HISTORICAL_BASELINE = {\n security: [5,4,4,3,3,4,8,18,26,22,20,18,22,24,21,19,23,28,25,20,16,12,9,6],\n immigration: [3,3,2,2,2,3,5,12,20,18,16,15,18,20,17,15,19,22,20,16,12,9,6,4],\n boarding: [2,2,2,1,1,2,4,8,12,10,9,9,11,12,10,9,11,13,12,10,8,6,4,3]\n};\n\n// --- 2. Day-of-week multiplier (0=Sun \u2026 6=Sat)\nconst DOW_MULTIPLIER = [1.15, 1.05, 1.0, 1.0, 1.05, 1.35, 1.40];\n\n// --- 3. Seasonal multiplier (month 0-indexed)\nconst SEASONAL_MULTIPLIER = [1.10,1.05,1.0,1.0,1.05,1.20,1.35,1.35,1.10,1.0,1.05,1.25];\n\n// --- 4. Parse inputs\nconst now = new Date();\nconst hour = now.getHours();\nconst dow = now.getDay();\nconst mon = now.getMonth();\n\nconst departureTime = new Date(item.departureTime || now.getTime() + 3 * 3600000);\nconst minutesToDeparture = Math.max(0, (departureTime - now) / 60000);\n\nconst liveData = item.liveQueueData || {};\nconst security = liveData.security || {};\nconst immigration = liveData.immigration || {};\nconst boarding = liveData.boarding || {};\n\n// --- 5. Compute live-queue multiplier for each checkpoint\nfunction liveMultiplier(queueLen, staffCount, defaultQ = 20, defaultS = 4) {\n const q = queueLen || defaultQ;\n const s = staffCount || defaultS;\n return Math.max(0.5, Math.min(3.5, (q / defaultQ) * (defaultS / s)));\n}\n\nconst secLiveMult = liveMultiplier(security.queueLength, security.staffCount, 30, 5);\nconst immLiveMult = liveMultiplier(immigration.queueLength, immigration.staffCount, 25, 4);\nconst brdLiveMult = liveMultiplier(boarding.queueLength, 4, 15, 4);\n\n// --- 6. Final predicted wait times (minutes) per checkpoint\nconst dowFactor = DOW_MULTIPLIER[dow] || 1.0;\nconst seaFactor = SEASONAL_MULTIPLIER[mon] || 1.0;\n\nfunction predict(baseline, liveMult) {\n return Math.round(baseline * liveMult * dowFactor * seaFactor);\n}\n\nconst secWait = predict(HISTORICAL_BASELINE.security[hour], secLiveMult);\nconst immWait = predict(HISTORICAL_BASELINE.immigration[hour], immLiveMult);\nconst brdWait = predict(HISTORICAL_BASELINE.boarding[hour], brdLiveMult);\n\nconst totalWait = secWait + immWait + brdWait;\nconst bufferMinutes = item.passengerType === 'premium' ? 20 : 35;\nconst recommendedArrivalMin = totalWait + bufferMinutes;\n\n// --- 7. Risk assessment\nconst willMissFlight = minutesToDeparture < (totalWait + 15);\nconst atRisk = minutesToDeparture < (totalWait + bufferMinutes);\n\n// --- 8. Congestion level label\nfunction congestionLabel(mins) {\n if (mins <= 10) return 'LOW';\n if (mins <= 20) return 'MODERATE';\n if (mins <= 35) return 'HIGH';\n return 'SEVERE';\n}\n\nconst overallCongestion = congestionLabel(totalWait);\n\n// --- 9. Optimal arrival window\nconst optimalArrivalTime = new Date(departureTime.getTime() - recommendedArrivalMin * 60000);\n\nreturn {\n json: {\n ...item,\n prediction: {\n security: { waitMinutes: secWait, congestion: congestionLabel(secWait) },\n immigration: { waitMinutes: immWait, congestion: congestionLabel(immWait) },\n boarding: { waitMinutes: brdWait, congestion: congestionLabel(brdWait) },\n totalWaitMinutes: totalWait,\n overallCongestion,\n recommendedArrivalMinutesBeforeFlight: recommendedArrivalMin,\n optimalArrivalTime: optimalArrivalTime.toISOString(),\n minutesToDeparture: Math.round(minutesToDeparture),\n riskFlags: {\n willMissFlight,\n atRisk,\n alertRequired: atRisk || willMissFlight\n },\n modelMeta: {\n hourOfDay: hour,\n dayOfWeek: dow,\n month: mon,\n dowMultiplier: dowFactor,\n seasonalMultiplier: seaFactor,\n computedAt: now.toISOString()\n }\n }\n }\n};\n"
},
"typeVersion": 2
},
{
"id": "98793a2e-c804-49df-8c8f-b9fc06202e59",
"name": "AI - Generate Traveler Alert Message",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
2336,
480
],
"parameters": {
"text": "=You are a friendly airport concierge AI. Using the structured prediction data below, write a clear, concise, and reassuring traveler alert message (max 120 words). Use plain language, include specific checkpoint wait times, and give a clear action \u2014 what the traveler should do right now.\n\nFlight: {{ $json.flightNumber }} departing {{ $json.departureTime }}\nAirport: {{ $json.airportCode }} Terminal {{ $json.terminal }}\nPassenger type: {{ $json.passengerType }}\n\nPredicted waits:\n\u2022 Security: {{ $json.prediction.security.waitMinutes }} min ({{ $json.prediction.security.congestion }})\n\u2022 Immigration: {{ $json.prediction.immigration.waitMinutes }} min ({{ $json.prediction.immigration.congestion }})\n\u2022 Boarding queue: {{ $json.prediction.boarding.waitMinutes }} min ({{ $json.prediction.boarding.congestion }})\n\u2022 Total queue time: {{ $json.prediction.totalWaitMinutes }} min\n\nRecommended airport arrival: {{ $json.prediction.recommendedArrivalMinutesBeforeFlight }} min before departure ({{ $json.prediction.optimalArrivalTime }})\nMinutes until departure: {{ $json.prediction.minutesToDeparture }}\nAt risk of missing flight: {{ $json.prediction.riskFlags.atRisk }}\nWill miss flight if not acting now: {{ $json.prediction.riskFlags.willMissFlight }}\n\nIf atRisk or willMissFlight is true, open with an urgent but calm warning. Otherwise open with a positive, informative tone. End with one practical tip for this specific terminal.",
"options": {},
"promptType": "define"
},
"typeVersion": 1.6
},
{
"id": "ca739c53-6a4e-4b19-ab14-862f6b8a8d0d",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
2080,
704
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-mini"
},
"options": {},
"builtInTools": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.3
},
{
"id": "db4a399c-cddb-4ab0-90e0-66717a0c2f0c",
"name": "JS - Format Alert Payload",
"type": "n8n-nodes-base.code",
"position": [
2736,
480
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// ============================================================\n// FORMAT FINAL ALERT PAYLOAD FOR DELIVERY & TRACKING\n// ============================================================\nconst item = $input.item.json;\n\nconst alertText = item.output\n || item.response\n || item.text\n || `\u26a0\ufe0f Your flight ${item.flightNumber} alert: total queue time ~${item.prediction?.totalWaitMinutes ?? '?'} min. Arrive by ${item.prediction?.optimalArrivalTime ?? 'ASAP'}.`;\n\nconst alertLevel = item.prediction?.riskFlags?.willMissFlight\n ? 'CRITICAL'\n : item.prediction?.riskFlags?.atRisk\n ? 'WARNING'\n : 'INFO';\n\nconst smsBody = `[${alertLevel}] ${alertText.substring(0, 160)}`;\n\nconst emailSubject = alertLevel === 'CRITICAL'\n ? `\ud83d\udea8 Act Now: Flight ${item.flightNumber} - You May Miss Your Flight`\n : alertLevel === 'WARNING'\n ? `\u26a0\ufe0f Heads Up: Queue Alert for Flight ${item.flightNumber}`\n : `\u2708\ufe0f Queue Update: Flight ${item.flightNumber} - ${item.prediction?.overallCongestion ?? ''} Congestion`;\n\nconst sheetRow = [\n new Date().toISOString().split('T')[0],\n item.requestId,\n item.flightNumber,\n item.airportCode,\n item.terminal,\n item.departureTime,\n item.prediction?.security?.waitMinutes ?? '',\n item.prediction?.immigration?.waitMinutes ?? '',\n item.prediction?.boarding?.waitMinutes ?? '',\n item.prediction?.totalWaitMinutes ?? '',\n item.prediction?.overallCongestion ?? '',\n item.prediction?.recommendedArrivalMinutesBeforeFlight ?? '',\n item.prediction?.optimalArrivalTime ?? '',\n alertLevel,\n new Date().toISOString()\n];\n\nreturn {\n json: {\n ...item,\n alertText,\n alertLevel,\n smsBody,\n emailSubject,\n sheetRow,\n deliveryStatus: 'PENDING'\n }\n};\n"
},
"typeVersion": 2
},
{
"id": "7ab9ce86-9293-4306-b9d5-581410fc0c2d",
"name": "Send Email Alert via SendGrid",
"type": "n8n-nodes-base.httpRequest",
"position": [
3008,
368
],
"parameters": {
"url": "https://api.sendgrid.com/v3/mail/send",
"method": "POST",
"options": {
"response": {
"response": {
"neverError": true
}
}
},
"jsonBody": "={\n \"personalizations\": [{\"to\": [{\"email\": \"{{ $json.travelerEmail }}\"}]}],\n \"from\": {\"email\": \"alerts@yourairportapp.com\", \"name\": \"Airport Queue Predictor\"},\n \"subject\": \"{{ $json.emailSubject }}\",\n \"content\": [{\"type\": \"text/plain\", \"value\": \"{{ $json.alertText }}\"}]\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_SENDGRID_API_KEY"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "532be173-e750-4629-9e28-ddbc74413164",
"name": "Log Prediction to Google Sheets",
"type": "n8n-nodes-base.httpRequest",
"position": [
3008,
608
],
"parameters": {
"url": "https://sheets.googleapis.com/v4/spreadsheets/YOUR_SHEET_ID/values/Predictions!A1:append?valueInputOption=USER_ENTERED",
"method": "POST",
"options": {
"response": {
"response": {
"neverError": true
}
}
},
"jsonBody": "={\n \"values\": [{{ JSON.stringify($json.sheetRow) }}]\n}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleSheetsOAuth2Api"
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "b0b1d24a-a2af-4085-8824-8597fee23a62",
"connections": {
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI - Generate Traveler Alert Message",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"JS - Format Alert Payload": {
"main": [
[
{
"node": "Send Email Alert via SendGrid",
"type": "main",
"index": 0
},
{
"node": "Log Prediction to Google Sheets",
"type": "main",
"index": 0
}
]
]
},
"Poll Airport Data Every 5 Min": {
"main": [
[
{
"node": "Normalise Traveler & Airport Context",
"type": "main",
"index": 0
}
]
]
},
"Wait 1 - API Rate Limit Buffer": {
"main": [
[
{
"node": "JS - Wait-Time Prediction Engine",
"type": "main",
"index": 0
}
]
]
},
"JS - Wait-Time Prediction Engine": {
"main": [
[
{
"node": "AI - Generate Traveler Alert Message",
"type": "main",
"index": 0
}
]
]
},
"Webhook - Traveler Queue Request": {
"main": [
[
{
"node": "Normalise Traveler & Airport Context",
"type": "main",
"index": 0
}
]
]
},
"AI - Generate Traveler Alert Message": {
"main": [
[
{
"node": "JS - Format Alert Payload",
"type": "main",
"index": 0
}
]
]
},
"Normalise Traveler & Airport Context": {
"main": [
[
{
"node": "Wait 1 - API Rate Limit Buffer",
"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.
googleSheetsOAuth2ApiopenAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow accepts traveler flight details via webhook or runs every 5 minutes, predicts airport queue wait times with a custom JavaScript model, generates an alert message using OpenAI, emails the traveler via SendGrid, and logs the prediction to Google Sheets. Receives a…
Source: https://n8n.io/workflows/16185/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
⏺ 🚀 How it works
L&D_AgentsAI_ATIVO. Uses httpRequest, agent, googleCalendarTool, toolSerpApi. Webhook trigger; 93 nodes.
CLINICAINTEGRAL_secretary. Uses postgres, mcpClientTool, googleDriveTool, toolWorkflow. Webhook trigger; 89 nodes.
Remi 1.1. Uses lmChatOpenAi, memoryPostgresChat, openAi, postgres. Webhook trigger; 89 nodes.
This n8n workflow orchestrates a powerful suite of AI Agents and automations to manage and optimize various aspects of an e-commerce operation, particularly for platforms like Shopify. It leverages La