AutomationFlowsEmail & Gmail › URL Uptime Monitor with Google Sheets & Gmail

URL Uptime Monitor with Google Sheets & Gmail

Original n8n title: URL Uptime Monitor

url-uptime-monitor. Uses scheduleTrigger, splitOut, googleSheets, summarize. Scheduled trigger; 18 nodes.

Cron / scheduled trigger★★★★☆ complexity18 nodesGoogle SheetsGmailHTTP RequestTelegram
Email & Gmail Trigger: Cron / scheduled Nodes: 18 Complexity: ★★★★☆ Added:

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

This workflow follows the Gmail → Google Sheets 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
{
  "name": "url-uptime-monitor",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 1
            },
            {}
          ]
        }
      },
      "id": "1c49ff18-191b-4795-bdbe-2c80be834427",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        416,
        560
      ],
      "typeVersion": 1.2
    },
    {
      "parameters": {
        "fieldToSplitOut": "urls",
        "options": {}
      },
      "id": "6f9a98c2-130d-49f6-9497-8e792f43263b",
      "name": "Split Out",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        896,
        464
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "mode": "raw",
        "jsonOutput": "{\n  \"urls\": {\n    \"URL 1\": \"https://cuongit.net\",\n    \"URL 2\": \"https://tech36.net\",\n    \"URL 3\": \"https://test.cuongit.net\", \n    \"URL 4\": \"https://test1.cuongit.net\"\n  }\n}",
        "options": {}
      },
      "id": "3cf83123-9829-4dba-96fc-820b72f91970",
      "name": "URLs",
      "type": "n8n-nodes-base.set",
      "position": [
        672,
        464
      ],
      "typeVersion": 3.4
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "",
          "mode": "url"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Monitorweb",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1audghXxGikfYkIMzGo-RANzCZCS2PablpGbr9uVQNBw/edit#gid=0"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "done": "=Done: {{ (Array.isArray($('Loop URLs').item.json.urls) ? $('Loop URLs').item.json.urls.join(', ') : $('Loop URLs').item.json.urls).toString().replace(/\\r?\\n/g, ' ') }}",
            "time": "={{ new Date().toLocaleString('sv-SE', { timeZone: 'Asia/Ho_Chi_Minh' }) }}"
          },
          "matchingColumns": [
            "time"
          ],
          "schema": [
            {
              "id": "time",
              "displayName": "time",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "error",
              "displayName": "error",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "done",
              "displayName": "done",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "id": "08cace4f-d4b9-4abe-b035-bc59ec260473",
      "name": "Success",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1664,
        624
      ],
      "typeVersion": 4.6,
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "",
          "mode": "url"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Monitorweb",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1audghXxGikfYkIMzGo-RANzCZCS2PablpGbr9uVQNBw/edit#gid=0"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "error": "=Error: {{ $json.urls }}",
            "time": "={{ new Date().toLocaleString('vi-VN', { timeZone: 'Asia/Ho_Chi_Minh', hour12: false }) }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "time",
              "displayName": "time",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "error",
              "displayName": "error",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "done",
              "displayName": "done",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "id": "6f910bbf-70bf-4002-8ade-fc28ed3ab50e",
      "name": "Error",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1664,
        848
      ],
      "typeVersion": 4.6,
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "fieldsToSummarize": {
          "values": [
            {
              "field": "done"
            },
            {
              "field": "error"
            }
          ]
        },
        "options": {}
      },
      "id": "12651162-043c-4e0a-8ef3-7bc8b7fdc04e",
      "name": "Total",
      "type": "n8n-nodes-base.summarize",
      "position": [
        1312,
        384
      ],
      "typeVersion": 1.1
    },
    {
      "parameters": {},
      "id": "3ae075a8-d4b5-4e38-a8cd-37cb1b3b2a3a",
      "name": "Run trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        416,
        368
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "subject": "Webs Down",
        "emailType": "text",
        "message": "=We inform you that the following URLs are currently down:\n{{ ($('Aggregate Errors').first().json.urls || []).join('\\n') }}\n\nError: {{ $('Total').first().json.count_error }}\nTime: {{ new Date().toLocaleString('vi-VN', { timeZone: 'Asia/Ho_Chi_Minh', hour12: false }) }}\n",
        "options": {}
      },
      "id": "3e06e308-db71-4b93-b36b-c4b4e25655f3",
      "name": "Send a message",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2080,
        512
      ],
      "typeVersion": 2.1,
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// 1) Fetch ALL items emitted by the \"Loop URLs\" node\nconst items = $items('Loop URLs'); // the node name must match exactly\nconsole.log(items);\n\n// 2) Extract the \"error\" property and drop empty/whitespace-only values\nconst errorUrls = items\n  .map(it => it.json.error)\n  .filter(url => typeof url === 'string' && url.trim().length);\n\nif (errorUrls.length === 0) {\n  throw new Error('No URLs were found in the \"error\" property.');\n}\n\n// Return one item per failed URL: { json: { url: \"<the url>\" } }\nreturn errorUrls.map(url => ({ json: { url } }));\n"
      },
      "id": "42d267ed-5d71-496c-a72e-d0727c56f86f",
      "name": "Code",
      "type": "n8n-nodes-base.code",
      "position": [
        1520,
        384
      ],
      "typeVersion": 2
    },
    {
      "parameters": {
        "fieldToSplitOut": "url",
        "options": {}
      },
      "id": "8032daea-87bf-4c60-954a-53617827b36d",
      "name": "Split Out2",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        1696,
        384
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "url": "={{ $json.urls }}",
        "options": {}
      },
      "id": "fe2cdc77-ec82-4a70-88ae-881974d18533",
      "name": "Request",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1296,
        592
      ],
      "typeVersion": 4.2,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "content": "## How it works (P1)\nBefore the loop, you enter the URLs to scan in the \"URLs\" stream, then start the trigger either manually or scheduled.",
        "width": 260,
        "color": 5
      },
      "id": "055e1a64-3c30-45e0-97a8-eb3aa7e9fcfa",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        640,
        688
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "content": "## How it works (P2)\nStart a loop for each URL entered, adding the status to a Google Sheet, then collect the status of each URL, filter out the crashes, and send an email, telegram with the crashes.",
        "height": 180,
        "width": 260
      },
      "id": "e3d65562-83a8-4b48-933f-f01215d893ba",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1920,
        816
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "content": "### Enter URLs to scan here",
        "height": 220,
        "width": 180,
        "color": 5
      },
      "id": "f1fd407b-548b-4f53-8781-43d9bd8e45ad",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        640,
        400
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "content": "### As a trigger you could also implement webhook or MCP",
        "height": 480,
        "width": 180,
        "color": 5
      },
      "id": "5914dd63-1292-4ef4-b15f-d57d6dc2d8bc",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        368,
        240
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "0afdf309-8ad9-4e41-b323-9970fb0bf631",
      "name": "Loop URLs",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        1088,
        464
      ],
      "typeVersion": 3
    },
    {
      "parameters": {
        "text": "=We inform you that the following URLs are currently down:  {{$items(\"Split Out2\").map(it => it.json.url.replace(/^Error:\\s*/, '')).join('\\n')}}\n\nError: {{ $('Total').first().json.count_error }} \nTime: {{ new Date().toLocaleString('vi-VN', { timeZone: 'Asia/Ho_Chi_Minh', hour12: false }) }}",
        "additionalFields": {}
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2064,
        208
      ],
      "id": "fc9cb246-8ec2-474a-b3e0-2304f6d957b3",
      "name": "Send a text message",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Aggregate Errors \u2014 preserve pairing so downstream nodes don't complain\nconst input = $input.all();\n\nconst urls = input\n  .map(it => String(it.json?.url || '').replace(/^(Error|Fail):\\s*/, ''))\n  .filter(Boolean);\n\n// If nothing came in, still return one item to avoid \"no items\" issues\nreturn [{\n  json: {\n    urls,\n    count_error: urls.length\n  },\n  // Preserve pairing information for *all* input items\n  pairedItem: input.map((_, idx) => ({ item: idx }))\n}];\n"
      },
      "id": "912a2064-16cc-4900-a023-0d7c840d76f4",
      "name": "Aggregate Errors",
      "type": "n8n-nodes-base.code",
      "position": [
        1872,
        384
      ],
      "typeVersion": 2
    }
  ],
  "connections": {
    "Code": {
      "main": [
        [
          {
            "node": "Split Out2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "URLs": {
      "main": [
        [
          {
            "node": "Split Out",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Error": {
      "main": [
        [
          {
            "node": "Loop URLs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Total": {
      "main": [
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Request": {
      "main": [
        [
          {
            "node": "Success",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Success": {
      "main": [
        [
          {
            "node": "Loop URLs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out": {
      "main": [
        [
          {
            "node": "Loop URLs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out2": {
      "main": [
        [
          {
            "node": "Aggregate Errors",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run trigger": {
      "main": [
        [
          {
            "node": "URLs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send a message": {
      "main": [
        []
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "URLs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop URLs": {
      "main": [
        [
          {
            "node": "Total",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Errors": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send a text message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {},
  "versionId": "a57dca2e-09ec-4a0d-bea2-7b395bb7c745",
  "meta": {
    "templateId": "5298",
    "templateCredsSetupCompleted": true
  },
  "id": "dTeyTnpDAIYf2r5K",
  "tags": []
}

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

How this works

Ensure your website's availability effortlessly with this workflow, which sends instant alerts to your team when URLs go down, preventing costly downtime and maintaining user trust. Ideal for web developers, site administrators, or digital marketers managing multiple online assets, it automates monitoring without constant manual checks. The key step involves a scheduled trigger that pings each URL via HTTP requests, logging results to Google Sheets and notifying via Gmail or Telegram if issues arise.

Use this when you oversee a handful of critical URLs needing daily checks, such as a company site or client portals, to catch problems early. Avoid it for high-frequency monitoring of hundreds of endpoints, where dedicated tools like Pingdom offer more scalability. Common variations include swapping Gmail for Slack notifications or adding a summary report emailed weekly for trend analysis.

About this workflow

url-uptime-monitor. Uses scheduleTrigger, splitOut, googleSheets, summarize. Scheduled trigger; 18 nodes.

Source: https://github.com/Cuongyd196/n8n-workflows/blob/main/workflows/devops/url-uptime-monitor/url-uptime-monitor.json — original creator credit. Request a take-down →

More Email & Gmail workflows → · Browse all categories →

Related workflows

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

Email & Gmail

Template - SSL Expiry Alert System. Uses googleSheets, scheduleTrigger, httpRequest, stickyNote. Scheduled trigger; 21 nodes.

Google Sheets, HTTP Request, Gmail +2
Email & Gmail

This workflow is ideal for administrators or IT professionals responsible for monitoring SSL certificates of multiple websites to ensure they do not expire unexpectedly.

Google Sheets, HTTP Request, Gmail +2
Email & Gmail

MPE Kleinanzeigen Unified (Reminder + Poster). Uses gmail, httpRequest, telegram, googleSheets. Scheduled trigger; 15 nodes.

Gmail, HTTP Request, Telegram +1
Email & Gmail

This automated n8n workflow continuously tracks real-time flight fare changes by querying airline APIs (e.g., Amadeus, Skyscanner). It compares new prices with historical fares and sends instant notif

HTTP Request, Telegram, Google Sheets +1
Email & Gmail

YOUR_ID 4. Uses gmail, googleDrive, googleSheets, httpRequest. Scheduled trigger; 53 nodes.

Gmail, Google Drive, Google Sheets +1