AutomationFlowsAI & RAG › Source and Approve Emergency Equipment Rentals with Telegram, Gmail and…

Source and Approve Emergency Equipment Rentals with Telegram, Gmail and…

Original n8n title: Source and Approve Emergency Equipment Rentals with Telegram, Gmail and Azure Openai

ByRahul Joshi @rahul08 on n8n.io

This workflow listens for emergency equipment breakdown requests via Telegram, emails multiple rental vendors through Gmail to request quotes, uses Azure OpenAI to compare responses, and sends the project manager an approval email with Approve/Reject links before notifying the…

Event trigger★★★★☆ complexityAI-powered25 nodesTelegram TriggerGmailTelegramLm Chat Azure Open AiAgentError TriggerSlack
AI & RAG Trigger: Event Nodes: 25 Complexity: ★★★★☆ AI nodes: yes Added:

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

This workflow follows the Agent → Error Trigger 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": "J7pc3fcPtm5rmjqn",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Emergency Equipment Rental Sourcing and Approval Workflow",
  "tags": [],
  "nodes": [
    {
      "id": "c37ab483-c33a-434d-9eaf-7d7df44c11b1",
      "name": "\ud83d\udccb Workflow Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -288,
        -304
      ],
      "parameters": {
        "width": 664,
        "height": 560,
        "content": "## \ud83c\udfd7\ufe0f Equipment Breakdown \u2014 Automated Rental Sourcing\n\n### How it works\nWhen a site foreman reports a machine breakdown via Telegram (or a web form), this workflow springs into action. It parses the request, fires rental quote emails to five vendors simultaneously, waits for responses, then feeds all quotes into an AI model that compares them and produces a ranked recommendation. The project manager receives a single approval email with a comparison table and one-click Approve / Reject buttons. On approval, the winning vendor gets a booking confirmation and the foreman gets a Telegram update.\n\n### Setup steps\n1. **Telegram Bot** \u2014 connect your bot credentials under *Telegram: Breakdown Report* and update the webhook.\n2. **Gmail OAuth2** \u2014 link your Google account to all three Gmail nodes (Send Vendor Emails, Email PM, Confirm Booking).\n3. **Azure OpenAI** \u2014 add your Azure endpoint and API key to the *Azure OpenAI Chat Model* credential.\n4. **Vendor list** \u2014 open *Split Into Vendor Items* and replace the placeholder vendor names and emails with your actual suppliers.\n5. **PM email** \u2014 in *Email PM: Comparison + Approval* and *Email: Rejection Notice*, replace the recipient address with your project manager's email.\n6. **Approval webhook URL** \u2014 in *Parse AI Analysis*, replace `YOUR-N8N-INSTANCE` with your live n8n domain.\n7. Do a test run using the Telegram command: `/breakdown Excavator | Site A | 3`"
      },
      "typeVersion": 1
    },
    {
      "id": "f0be476d-ae48-4c10-98a4-3a895b77b8ed",
      "name": "Section: Intake",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        528,
        240
      ],
      "parameters": {
        "color": 7,
        "width": 536,
        "height": 580,
        "content": "## \ud83d\udce1 Intake & Normalisation\nAccepts breakdown reports from two entry points \u2014 a Telegram bot command or a web form \u2014 and normalises both into a single consistent data shape. Generates a unique Breakdown ID and timestamps the report before anything else runs."
      },
      "typeVersion": 1
    },
    {
      "id": "7325b770-0142-41a4-8e30-18781ef26e60",
      "name": "Section: Vendor Outreach",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1088,
        224
      ],
      "parameters": {
        "color": 7,
        "width": 920,
        "height": 596,
        "content": "## \ud83d\udce7 Vendor Outreach\nSplits the request into one item per vendor and sends each a formatted HTML quote-request email via Gmail. The workflow then pauses and waits for replies before proceeding \u2014 giving vendors a defined window to respond."
      },
      "typeVersion": 1
    },
    {
      "id": "5d011454-df46-474c-b331-5631a0724e7d",
      "name": "Section: AI Analysis",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2048,
        192
      ],
      "parameters": {
        "color": 7,
        "width": 816,
        "height": 728,
        "content": "## \ud83e\udd16 AI Quote Analysis\nCollects all vendor responses, filters out unavailable suppliers, then passes the shortlist to an Azure OpenAI model. The AI returns a structured JSON payload containing an HTML comparison table, a recommended vendor with rationale, risk flags, and an executive summary \u2014 ready to drop straight into the PM's approval email."
      },
      "typeVersion": 1
    },
    {
      "id": "e097eda8-4b74-4324-a36f-8a28f6c70f89",
      "name": "Section: Approval & Booking",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2912,
        96
      ],
      "parameters": {
        "color": 7,
        "width": 864,
        "height": 852,
        "content": "## \u2705 PM Approval & Booking\nEmails the project manager a full vendor comparison with one-click Approve / Reject links. On approval, a booking confirmation goes to the winning vendor and the foreman receives a Telegram notification. On rejection, a manual-review alert is sent instead."
      },
      "typeVersion": 1
    },
    {
      "id": "eb315088-3457-4011-ba04-4dc9344fcc7b",
      "name": "Credentials & Security",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3520,
        1024
      ],
      "parameters": {
        "color": 3,
        "width": 280,
        "height": 256,
        "content": "## \ud83d\udd10 Credentials & Security\nUse OAuth2 for Gmail and a scoped Azure OpenAI API key. Store all secrets in n8n credentials \u2014 never hardcode them in code nodes. Replace all placeholder emails and vendor addresses before publishing or sharing this template."
      },
      "typeVersion": 1
    },
    {
      "id": "8cccc723-7c50-4503-9a86-d730fe1865a7",
      "name": "Telegram: Breakdown Report",
      "type": "n8n-nodes-base.telegramTrigger",
      "notes": "Listens for /breakdown command from foreman. Message format: /breakdown [machine_type] | [site_name] | [duration_days]",
      "position": [
        624,
        496
      ],
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "026cd13b-4989-413a-8805-22e10e81c05c",
      "name": "Normalise Intake Data",
      "type": "n8n-nodes-base.code",
      "position": [
        880,
        496
      ],
      "parameters": {
        "jsCode": "// Normalise data from either Telegram or Form trigger\nconst item = $input.first();\nconst data = item.json;\n\nlet machineType, siteName, durationDays, urgency, foremanName, foremanChatId;\n\n// Detect source: Telegram has 'message' key, Form has 'formTitle'\nif (data.message) {\n  // Parse Telegram message: /breakdown [machine_type] | [site_name] | [duration_days]\n  const text = data.message.text || '';\n  const parts = text.replace('/breakdown', '').trim().split('|').map(s => s.trim());\n  machineType = parts[0] || 'Unknown Machine';\n  siteName = parts[1] || 'Unknown Site';\n  durationDays = parseInt(parts[2]) || 1;\n  urgency = 'Critical - Needed Today';\n  foremanName = data.message.from?.first_name + ' ' + (data.message.from?.last_name || '');\n  foremanChatId = String(data.message.chat?.id || '');\n} else {\n  // Form submission\n  machineType = data['Machine Type'] || 'Unknown Machine';\n  siteName = data['Site Name'] || 'Unknown Site';\n  durationDays = parseInt(data['Duration Needed (Days)']) || 1;\n  urgency = data['Urgency'] || 'Standard - Needed Within 48 Hours';\n  foremanName = data['Foreman Name'] || 'Foreman';\n  foremanChatId = data['Foreman Telegram Chat ID'] || '';\n}\n\nconst breakdownId = 'BRK-' + Date.now();\nconst reportedAt = new Date().toISOString();\n\nreturn [{\n  json: {\n    breakdownId,\n    machineType,\n    siteName,\n    durationDays,\n    urgency,\n    foremanName,\n    foremanChatId,\n    reportedAt,\n    vendorQuotes: [],\n    source: data.message ? 'telegram' : 'form'\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "a633e8cc-9b1c-4150-bbdb-51e7420ff6c4",
      "name": "Split Into Vendor Items",
      "type": "n8n-nodes-base.code",
      "position": [
        1120,
        496
      ],
      "parameters": {
        "jsCode": "// Build individual vendor email payloads\n// Replace these with your actual vendor emails and names\nconst vendors = [\n  { name: 'Alpha Heavy Equipment Rentals', email: 'rentals@example-alpha.com' },\n  { name: 'Beta Crane Hire Co.', email: 'hire@example-beta.com' },\n  { name: 'Gamma Machinery Rentals', email: 'info@example-gamma.com' },\n  { name: 'Delta Equipment Solutions', email: 'quotes@example-delta.com' },\n  { name: 'Sigma Plant Hire', email: 'plant@example-sigma.com' }\n];\n\nconst intake = $input.first().json;\n\nreturn vendors.map(vendor => ({\n  json: {\n    ...intake,\n    vendor\n  }\n}));\n"
      },
      "typeVersion": 2
    },
    {
      "id": "421d273f-e009-469a-a676-727b915585a4",
      "name": "Send Vendor Quote Request Emails",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1344,
        496
      ],
      "parameters": {
        "sendTo": "=vendor@example.com",
        "message": "=<html>\n<body style=\"font-family: Arial, sans-serif; color: #333;\">\n  <h2 style=\"color: #c0392b;\">\u26a0\ufe0f Urgent Equipment Rental Request</h2>\n  <p>Dear {{ $json.vendor.name }} Team,</p>\n  <p>We have an emergency equipment breakdown and require an immediate rental replacement. Please reply to this email with your availability and pricing as soon as possible.</p>\n  \n  <table style=\"border-collapse: collapse; width: 100%; margin: 20px 0;\">\n    <tr style=\"background: #f2f2f2;\"><th style=\"padding: 10px; border: 1px solid #ddd; text-align: left;\">Field</th><th style=\"padding: 10px; border: 1px solid #ddd; text-align: left;\">Details</th></tr>\n    <tr><td style=\"padding: 10px; border: 1px solid #ddd;\"><strong>Reference ID</strong></td><td style=\"padding: 10px; border: 1px solid #ddd;\">{{ $json.breakdownId }}</td></tr>\n    <tr><td style=\"padding: 10px; border: 1px solid #ddd;\"><strong>Machine Required</strong></td><td style=\"padding: 10px; border: 1px solid #ddd;\">{{ $json.machineType }}</td></tr>\n    <tr><td style=\"padding: 10px; border: 1px solid #ddd;\"><strong>Site Location</strong></td><td style=\"padding: 10px; border: 1px solid #ddd;\">{{ $json.siteName }}</td></tr>\n    <tr><td style=\"padding: 10px; border: 1px solid #ddd;\"><strong>Duration Needed</strong></td><td style=\"padding: 10px; border: 1px solid #ddd;\">{{ $json.durationDays }} day(s)</td></tr>\n    <tr><td style=\"padding: 10px; border: 1px solid #ddd;\"><strong>Urgency</strong></td><td style=\"padding: 10px; border: 1px solid #ddd;\"><strong style=\"color: #c0392b;\">{{ $json.urgency }}</strong></td></tr>\n    <tr><td style=\"padding: 10px; border: 1px solid #ddd;\"><strong>Reported At</strong></td><td style=\"padding: 10px; border: 1px solid #ddd;\">{{ $json.reportedAt }}</td></tr>\n  </table>\n  \n  <p><strong>Please reply with:</strong></p>\n  <ol>\n    <li>Availability (Yes / No / Partial)</li>\n    <li>Daily rental rate (inc. delivery)</li>\n    <li>Earliest delivery time to site</li>\n    <li>Any additional conditions</li>\n  </ol>\n  \n  <p style=\"color: #888; font-size: 12px;\">This is an automated request. Please reply directly to this email. Reference ID: {{ $json.breakdownId }}</p>\n</body>\n</html>",
        "options": {
          "appendAttribution": false
        },
        "subject": "=\ud83d\udea8 Urgent Rental Request \u2013 {{ $json.machineType }} | {{ $json.siteName }} | Ref: {{ $json.breakdownId }}",
        "operation": "sendAndWait"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "0b1eebb1-ab4f-4c97-ab1d-f29ac4ef508c",
      "name": "Aggregate Vendor Quotes",
      "type": "n8n-nodes-base.code",
      "position": [
        2096,
        480
      ],
      "parameters": {
        "jsCode": "// Aggregate all vendor quotes collected during the wait period\n// In production, quotes arrive via the webhook and are stored in a Google Sheet or memory\n// This node reads them and compiles them for GPT-4o\n\nconst intake = $input.first().json;\n\n// Sample structure - in production these come from webhook POSTs\n// Replace with actual $json.body data from webhook\nconst collectedQuotes = intake.vendorQuotes && intake.vendorQuotes.length > 0\n  ? intake.vendorQuotes\n  : [\n      // Fallback demo data structure if no real quotes yet\n      { vendor: 'Alpha Heavy Equipment Rentals', available: true, dailyRate: 4500, deliveryHours: 4, notes: 'Includes operator, fuel extra' },\n      { vendor: 'Beta Crane Hire Co.', available: true, dailyRate: 3900, deliveryHours: 6, notes: 'No operator, standard insurance' },\n      { vendor: 'Gamma Machinery Rentals', available: false, dailyRate: null, deliveryHours: null, notes: 'Not available this week' }\n    ];\n\nconst availableQuotes = collectedQuotes.filter(q => q.available !== false);\n\nreturn [{\n  json: {\n    ...intake,\n    collectedQuotes,\n    availableQuotes,\n    quotesCollectedAt: new Date().toISOString()\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "1c6998d5-d21b-4e9b-b793-6363471dff3c",
      "name": "Parse AI Analysis",
      "type": "n8n-nodes-base.code",
      "position": [
        2720,
        480
      ],
      "parameters": {
        "jsCode": "// Parse GPT-4o JSON response and merge with intake data\nconst item = $input.first();\nconst raw = item.json.message?.content || item.json.choices?.[0]?.message?.content || '';\n\nlet analysis;\ntry {\n  const cleaned = raw.replace(/```json|```/g, '').trim();\n  analysis = JSON.parse(cleaned);\n} catch (e) {\n  analysis = {\n    comparisonTableHtml: '<p>Unable to parse vendor quotes.</p>',\n    recommendedVendor: 'Manual Review Required',\n    recommendedVendorRationale: raw,\n    riskFlags: ['AI parsing error - please review manually'],\n    executiveSummary: raw\n  };\n}\n\n// Build approval URL (replace with your actual n8n webhook URL after deployment)\nconst approvalBaseUrl = 'https://YOUR-N8N-INSTANCE/webhook/approve-rental';\nconst approvalUrl = `${approvalBaseUrl}?id=${item.json.breakdownId}&vendor=${encodeURIComponent(analysis.recommendedVendor)}&approved=true`;\nconst rejectUrl = `${approvalBaseUrl}?id=${item.json.breakdownId}&approved=false`;\n\nreturn [{\n  json: {\n    ...item.json,\n    analysis,\n    approvalUrl,\n    rejectUrl\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "7768fe57-c763-4c2d-b436-f7c6eb9ea619",
      "name": "Email PM: Vendor Comparison + Approval Request",
      "type": "n8n-nodes-base.gmail",
      "notes": "Change sendTo to your actual PM email address",
      "position": [
        2912,
        480
      ],
      "parameters": {
        "sendTo": "pm@example.com",
        "message": "=<html>\n<body style=\"font-family: Arial, sans-serif; color: #333; max-width: 800px;\">\n  <h2 style=\"color: #2c3e50;\">Equipment Rental Approval Required</h2>\n  \n  <div style=\"background: #eaf4fb; padding: 15px; border-left: 4px solid #2980b9; margin-bottom: 20px;\">\n    <strong>\ud83d\udccb Breakdown Summary</strong><br/>\n    Machine: <strong>{{ $('Normalise Intake Data').item.json.machineType }}  </strong> | Site: <strong>   {{ $('Normalise Intake Data').item.json.siteName }} </strong> | Duration: <strong {{ $('Normalise Intake Data').item.json.durationDays }}day(s)</strong> | Urgency: <strong style=\"color:#c0392b;\">{{ $('Normalise Intake Data').item.json.urgency }} </strong><br/>\n    Reported by: {{ $('Normalise Intake Data').item.json.foremanName }} at{{ $('Normalise Intake Data').item.json.reportedAt }}\n  </div>\n  \n  <h3>\ud83d\udcca Vendor Comparison</h3>\n  {{ $json.analysis.comparisonTableHtml }}\n  \n  <div style=\"background: #eafaf1; padding: 15px; border-left: 4px solid #27ae60; margin: 20px 0;\">\n    <strong>\u2705 AI Recommendation: {{ $json.analysis.recommendedVendor }}</strong><br/>\n    {{ $json.analysis.recommendedVendorRationale }}\n  </div>\n  \n  {% if $json.analysis.riskFlags.length > 0 %}\n  <div style=\"background: #fef9e7; padding: 15px; border-left: 4px solid #f39c12; margin: 20px 0;\">\n    <strong>\u26a0\ufe0f Risk Flags:</strong>\n    <ul>{% for flag in $json.analysis.riskFlags %}<li>  {{ $json.analysis.riskFlags[0] }}</li>{% endfor %}</ul>\n  </div>\n  {% endif %}\n  \n  <p><strong>Executive Summary:</strong><br/>{{ $json.analysis.executiveSummary }}</p>\n  \n  <div style=\"margin: 30px 0;\">\n    <a href=\"{{ $json.approvalUrl }}\" style=\"background: #27ae60; color: white; padding: 14px 28px; text-decoration: none; border-radius: 6px; font-size: 16px; margin-right: 15px;\">\u2705 APPROVE BOOKING</a>\n    <a href=\"{{ $json.rejectUrl }}\" style=\"background: #e74c3c; color: white; padding: 14px 28px; text-decoration: none; border-radius: 6px; font-size: 16px;\">\u274c REJECT / MANUAL REVIEW</a>\n  </div>\n  \n  <p style=\"color: #888; font-size: 12px;\">This is an automated recommendation. Reference: {{ $json.breakdownId }}</p>\n</body>\n</html>",
        "options": {
          "appendAttribution": false
        },
        "subject": "=\u26a1 Approval Required: Equipment Rental for {{ $('Split Into Vendor Items').item.json.machineType }} |  {{ $('Split Into Vendor Items').item.json.siteName }}| Ref: {{ $('Split Into Vendor Items').item.json.breakdownId }}",
        "operation": "sendAndWait"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "fcd26c00-a664-4c49-9936-ae44ce5cc47f",
      "name": "Approved?",
      "type": "n8n-nodes-base.if",
      "position": [
        3312,
        480
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "approval-check",
              "operator": {
                "type": "boolean",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.data.approved }}",
              "rightValue": "true"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "f96a93b4-db53-494d-b0fb-fcabc24d822c",
      "name": "Send Booking Confirmation to Vendor",
      "type": "n8n-nodes-base.gmail",
      "position": [
        3584,
        464
      ],
      "parameters": {
        "sendTo": "=vendor@example.com",
        "message": "=<html>\n<body style=\"font-family: Arial, sans-serif; color: #333;\">\n  <h2 style=\"color: #27ae60;\">\u2705 Rental Booking Confirmed</h2>\n  <p>Dear {{ $json.analysis.recommendedVendor }} Team,</p>\n  <p>We are pleased to confirm the following rental booking. Please proceed with delivery arrangements as per the details below.</p>\n  \n  <table style=\"border-collapse: collapse; width: 100%; margin: 20px 0;\">\n    <tr style=\"background: #f2f2f2;\"><th style=\"padding: 10px; border: 1px solid #ddd; text-align: left;\">Field</th><th style=\"padding: 10px; border: 1px solid #ddd; text-align: left;\">Details</th></tr>\n    <tr><td style=\"padding: 10px; border: 1px solid #ddd;\"><strong>Booking Reference</strong></td><td style=\"padding: 10px; border: 1px solid #ddd;\">{{ $('Split Into Vendor Items').item.json.breakdownId }} </td></tr>\n    <tr><td style=\"padding: 10px; border: 1px solid #ddd;\"><strong>Machine Required</strong></td><td style=\"padding: 10px; border: 1px solid #ddd;\">{{ $('Split Into Vendor Items').item.json.machineType }} </td></tr>\n    <tr><td style=\"padding: 10px; border: 1px solid #ddd;\"><strong>Delivery Site</strong></td><td style=\"padding: 10px; border: 1px solid #ddd;\">{{ $('Split Into Vendor Items').item.json.siteName }} </td></tr>\n    <tr><td style=\"padding: 10px; border: 1px solid #ddd;\"><strong>Rental Duration</strong></td><td style=\"padding: 10px; border: 1px solid #ddd;\">{{ $('Split Into Vendor Items').item.json.durationDays }} day(s)</td></tr>\n    <tr><td style=\"padding: 10px; border: 1px solid #ddd;\"><strong>Approved At</strong></td><td style=\"padding: 10px; border: 1px solid #ddd;\">{{ new Date().toISOString() }}</td></tr>\n  </table>\n  \n  <p>Please confirm receipt and provide your expected delivery time by return email.</p>\n  <p>Thank you for your prompt response.</p>\n  \n  <p style=\"color: #888; font-size: 12px;\">Reference: {{ $json.breakdownId }}</p>\n</body>\n</html>",
        "options": {
          "appendAttribution": false
        },
        "subject": "=\u2705 Booking Confirmed \u2013 {{ $('Split Into Vendor Items').item.json.machineType }} |  {{ $('Split Into Vendor Items').item.json.siteName }}| Ref: {{ $('Split Into Vendor Items').item.json.breakdownId }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "e248398a-fe62-48d0-b19d-22f61fd0ca2c",
      "name": "Notify Foreman via Telegram: Booking Confirmed",
      "type": "n8n-nodes-base.telegram",
      "notes": "Only fires if foreman submitted via Telegram and provided chat ID",
      "position": [
        3584,
        192
      ],
      "parameters": {
        "text": "=\u2705 *Rental Confirmed!*\n\nYour breakdown report ({{ $('Split Into Vendor Items').item.json.breakdownId }}) has been processed.\n\n\ud83c\udfd7 *Machine:* {{ $('Split Into Vendor Items').item.json.machineType }}\n\ud83d\udccd *Site:*{{ $('Split Into Vendor Items').item.json.siteName }}\n\ud83c\udfe2 *Vendor:* {{ $('Aggregate Vendor Quotes').item.json.availableQuotes[0].vendor }}\n\ud83d\udcc5 *Duration:*  {{ $('Split Into Vendor Items').item.json.durationDays }}day(s)\n\nConfirmation has been sent to the vendor. Delivery details will follow shortly.",
        "chatId": "={{ $('Telegram: Breakdown Report').item.json.message.chat.id }}",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "ea2ce7e2-a881-4127-8414-aa1aa5238aba",
      "name": "Email PM: Rejection Notice",
      "type": "n8n-nodes-base.gmail",
      "position": [
        3584,
        736
      ],
      "parameters": {
        "sendTo": "pm@example.com",
        "message": "=<html>\n<body style=\"font-family: Arial, sans-serif; color: #333;\">\n  <h2 style=\"color: #e74c3c;\">Rental Booking Rejected \u2013 Manual Review Required</h2>\n  <p>The automated rental recommendation for <strong>{{ $json.machineType }}</strong> at <strong>{{ $json.siteName }}</strong> was rejected.</p>\n  <p><strong>Reference:</strong> {{ $json.breakdownId }}</p>\n  <p>Please contact vendors directly or re-run the workflow with updated parameters.</p>\n  <p style=\"color: #888; font-size: 12px;\">This is an automated notification.</p>\n</body>\n</html>",
        "options": {
          "appendAttribution": false
        },
        "subject": "=\u2139\ufe0f Rental Request Rejected \u2013 Manual Review Required | {{ $json.breakdownId }}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "ec7b236a-7cae-4667-a9fc-962370917692",
      "name": "Azure OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatAzureOpenAi",
      "position": [
        2320,
        752
      ],
      "parameters": {
        "model": "gpt-4o-mini",
        "options": {}
      },
      "credentials": {
        "azureOpenAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "7cda3ba9-584a-4ad0-acb8-284e41daf79a",
      "name": "AI Agent: Analyse & Rank Vendor Quotes",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2400,
        480
      ],
      "parameters": {
        "text": "=You are a construction procurement analyst specialising in equipment rentals.\n\nBreakdown Details:\n- Machine Required: {{ $('Split Into Vendor Items').item.json.machineType }}\n- Site: {{ $('Split Into Vendor Items').item.json.siteName }}\n- Duration:{{ $('Split Into Vendor Items').item.json.durationDays }}day(s)\n- Urgency:{{ $('Split Into Vendor Items').item.json.urgency }}\n- Breakdown ID: {{ $('Split Into Vendor Items').item.json.breakdownId }}\n\nAvailable Vendor Quotes:\n{{ JSON.stringify($json.availableQuotes, null, 2) }}\n\nAnalyse the quotes and respond with ONLY valid JSON \u2014 no markdown, no explanation, no code fences:\n\n{\n  \"comparisonTableHtml\": \"<table style='border-collapse:collapse;width:100%'><tr style='background:#f2f2f2'><th style='padding:10px;border:1px solid #ddd'>Vendor</th><th style='padding:10px;border:1px solid #ddd'>Daily Rate</th><th style='padding:10px;border:1px solid #ddd'>Total Cost</th><th style='padding:10px;border:1px solid #ddd'>Delivery (hrs)</th><th style='padding:10px;border:1px solid #ddd'>Notes</th></tr>... rows here ...</table>\",\n  \"recommendedVendor\": \"Vendor Name Here\",\n  \"recommendedVendorRationale\": \"2-3 sentence justification covering price, delivery speed, and conditions\",\n  \"riskFlags\": [\"flag1 if any\", \"flag2 if any\"],\n  \"executiveSummary\": \"One paragraph for the Project Manager summarising the situation and recommended action\"\n}",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 2.1
    },
    {
      "id": "7fd0270e-97ce-4df0-bb5d-69ee4375fcd6",
      "name": "Wait for Vendor Reply Window",
      "type": "n8n-nodes-base.wait",
      "position": [
        1568,
        496
      ],
      "parameters": {},
      "typeVersion": 1.1
    },
    {
      "id": "6c62507e-e6a3-49fe-91c3-4cc34689d862",
      "name": "If",
      "type": "n8n-nodes-base.if",
      "position": [
        1840,
        496
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "05adb9d2-ad13-40a3-822f-ee052cdcf4db",
              "operator": {
                "type": "boolean",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.data.approved }}",
              "rightValue": false
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "afbdc956-9330-466e-a318-eef5bcfc8b57",
      "name": "Wait for PM Approval Response",
      "type": "n8n-nodes-base.wait",
      "position": [
        3104,
        480
      ],
      "parameters": {},
      "typeVersion": 1.1
    },
    {
      "id": "db50116c-babf-4197-bafa-4205e7ffcede",
      "name": "On Workflow Error",
      "type": "n8n-nodes-base.errorTrigger",
      "position": [
        640,
        1216
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "bd7b1ee8-b725-46d8-8988-bd904ea77b36",
      "name": "Slack \u2013 Send Error Alert",
      "type": "n8n-nodes-base.slack",
      "position": [
        896,
        1216
      ],
      "parameters": {
        "text": "=error in the workflow",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0AN1UGL0RM",
          "cachedResultName": "all-n8n-automations"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "f979a636-d104-4dbd-aaec-7d75d1ce5b3c",
      "name": "Section: Error Handler",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        544,
        1056
      ],
      "parameters": {
        "color": 7,
        "width": 556,
        "height": 368,
        "content": "## \u26a0\ufe0f Error Handler\nCatches any failure in the workflow and posts a Slack alert with the error message, failing node name, and execution ID. Wire the error output of any critical node here to prevent silent failures going unnoticed."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "2259b8fb-3db8-40fb-a9c4-9c1b6990b406",
  "connections": {
    "If": {
      "main": [
        [
          {
            "node": "Aggregate Vendor Quotes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Approved?": {
      "main": [
        [
          {
            "node": "Notify Foreman via Telegram: Booking Confirmed",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send Booking Confirmation to Vendor",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Email PM: Rejection Notice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "On Workflow Error": {
      "main": [
        [
          {
            "node": "Slack \u2013 Send Error Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI Analysis": {
      "main": [
        [
          {
            "node": "Email PM: Vendor Comparison + Approval Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalise Intake Data": {
      "main": [
        [
          {
            "node": "Split Into Vendor Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Vendor Quotes": {
      "main": [
        [
          {
            "node": "AI Agent: Analyse & Rank Vendor Quotes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Azure OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent: Analyse & Rank Vendor Quotes",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Split Into Vendor Items": {
      "main": [
        [
          {
            "node": "Send Vendor Quote Request Emails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram: Breakdown Report": {
      "main": [
        [
          {
            "node": "Normalise Intake Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for Vendor Reply Window": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for PM Approval Response": {
      "main": [
        [
          {
            "node": "Approved?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Vendor Quote Request Emails": {
      "main": [
        [
          {
            "node": "Wait for Vendor Reply Window",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent: Analyse & Rank Vendor Quotes": {
      "main": [
        [
          {
            "node": "Parse AI Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email PM: Vendor Comparison + Approval Request": {
      "main": [
        [
          {
            "node": "Wait for PM Approval Response",
            "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

This workflow listens for emergency equipment breakdown requests via Telegram, emails multiple rental vendors through Gmail to request quotes, uses Azure OpenAI to compare responses, and sends the project manager an approval email with Approve/Reject links before notifying the…

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

Send a message or a voice note on Telegram right after the meeting. n8n transcribes (if it's a voice note) and sends the text to GPT. GPT generates a structured and professional meeting minutes report

OpenAI Chat, Gmail, Telegram Trigger +6
AI & RAG

This workflow is an AI-powered virtual cinematography and previs generation pipeline designed for film and VFX production. It transforms a director’s shot description into multiple camera choreography

Agent, Lm Chat Azure Open Ai, HTTP Request +7
AI & RAG

This workflow is perfect for: Small to medium businesses looking to automate customer support E-commerce stores handling order inquiries and customer questions SaaS companies providing technical suppo

Telegram Trigger, Agent, OpenAI Chat +8
AI & RAG

This workflow collects a construction Scope of Work PDF via an n8n form, uses Azure OpenAI to extract BOQ-style line items, prices them using a Google Sheets unit-rate database, generates a formatted

Lm Chat Azure Open Ai, Agent, Form Trigger +5
AI & RAG

Automate real-time cryptocurrency analysis by turning Telegram messages into professional, AI-generated market reports. 📈🤖 This workflow listens to user queries in Telegram, classifies intent using AI

Telegram Trigger, HTTP Request, Telegram +5