AutomationFlowsEmail & Gmail › Monitor Website Uptime with Google Sheets and Gmail Alerts

Monitor Website Uptime with Google Sheets and Gmail Alerts

ByArbaz Asif @arbaz on n8n.io

Webpage Down Detector automatically monitors your website pages and alerts you if any go down. This workflow saves time, prevents silent downtime, and keeps your website reliable.

Cron / scheduled trigger★★★★☆ complexity13 nodesGoogle SheetsGmail
Email & Gmail Trigger: Cron / scheduled Nodes: 13 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #11380 — 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
{
  "id": "Twcq80VMRVqPoakz",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Webpage Down Detector - revised",
  "tags": [],
  "nodes": [
    {
      "id": "08dfb3ba-fa13-4aa7-bcf4-dc1cf3bbb34e",
      "name": "Sticky Note \u2014 Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -288,
        -192
      ],
      "parameters": {
        "width": 500,
        "height": 432,
        "content": "## Webpage Down Detector\n\nMonitors all your webpages daily and emails a detailed alert if any are down.\n\n**Features:**\n- Checks all URLs from Google Sheets\n- Retries failed URLs once to avoid false positives\n- Sends a rich HTML email with status codes & response times\n- Sends nothing if all pages are up (no noise)\n- Includes timestamp and summary stats\n\n**Setup:**\n1. Add your URLs to the Google Sheet\n2. Set your Google Sheets credentials\n3. Set your Gmail credentials\n4. Update recipient email in the Gmail node"
      },
      "typeVersion": 1
    },
    {
      "id": "61fdaaa8-1482-4a1f-9896-2edfae21f774",
      "name": "Sticky Note \u2014 Step 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        240,
        -192
      ],
      "parameters": {
        "color": 7,
        "width": 352,
        "height": 424,
        "content": "## Step 1\nRuns every day at 8am and fetches all your URLs from Google Sheets"
      },
      "typeVersion": 1
    },
    {
      "id": "c820ef85-635a-465b-936d-30f4bc63adad",
      "name": "Sticky Note \u2014 Step 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        624,
        -192
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 424,
        "content": "## Step 2\nChecks each URL. If it fails, retries once after 5 seconds to avoid false positives"
      },
      "typeVersion": 1
    },
    {
      "id": "27fee5f8-a621-4f05-a735-e98eb81d84d5",
      "name": "Sticky Note \u2014 Step 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        992,
        -192
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 424,
        "content": "## Step 3\nFilters down pages, checks if any exist, then sends a rich HTML alert email with full details"
      },
      "typeVersion": 1
    },
    {
      "id": "ab8b8774-2674-4c34-93b7-56c5f232e269",
      "name": "Runs Every Day at 8am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        288,
        -16
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * *"
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "04e9e21b-e003-4fc8-94c0-bdfbc6298afb",
      "name": "Fetch All Webpages",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        464,
        -16
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1bn40GRB-Snm7eT9buivMVY-Ba1cao7i-0wGIqHozrD4/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1bn40GRB-Snm7eT9buivMVY-Ba1cao7i-0wGIqHozrD4",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1bn40GRB-Snm7eT9buivMVY-Ba1cao7i-0wGIqHozrD4/edit?usp=drivesdk",
          "cachedResultName": "urls"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "3f42dab3-c215-42b8-aeca-d59af98b2079",
      "name": "Check All Webpages (with Retry)",
      "type": "n8n-nodes-base.code",
      "position": [
        752,
        -16
      ],
      "parameters": {
        "jsCode": "// Check each URL and retry once if it fails\n// Records status code and response time\n\nasync function fetchStatus(url) {\n  const start = Date.now();\n  try {\n    await this.helpers.request({\n      method: 'GET',\n      url: url,\n      timeout: 10000,\n      rejectUnauthorized: false,\n    });\n    return { status: 200, responseTime: Date.now() - start };\n  } catch (error) {\n    return { status: error.statusCode || 'ERROR', responseTime: Date.now() - start };\n  }\n}\n\nasync function sleep(ms) {\n  return new Promise(resolve => setTimeout(resolve, ms));\n}\n\nconst results = await Promise.all(\n  items.map(async (item) => {\n    const url = item.json.URL;\n    if (!url) return null;\n\n    // First attempt\n    let result = await fetchStatus.call(this, url);\n\n    // Retry once if failed\n    const isDown = result.status === 'ERROR' || (typeof result.status === 'number' && (result.status < 200 || result.status >= 400));\n    if (isDown) {\n      await sleep(5000); // wait 5 seconds before retry\n      result = await fetchStatus.call(this, url);\n    }\n\n    const stillDown = result.status === 'ERROR' || (typeof result.status === 'number' && (result.status < 200 || result.status >= 400));\n\n    return {\n      json: {\n        URL: url,\n        status: result.status,\n        responseTime: result.responseTime,\n        isDown: stillDown,\n        retried: isDown\n      }\n    };\n  })\n);\n\nreturn results.filter(r => r !== null);\n"
      },
      "typeVersion": 2
    },
    {
      "id": "6dfa15f9-bb03-4a46-b800-42a11143bf84",
      "name": "Filter Down Webpages",
      "type": "n8n-nodes-base.code",
      "position": [
        1088,
        -16
      ],
      "parameters": {
        "jsCode": "// Separate down pages from up pages\n// Pass both counts forward for the summary email\n\nconst allPages = items.map(i => i.json);\nconst downPages = allPages.filter(p => p.isDown);\nconst upCount = allPages.length - downPages.length;\n\nif (downPages.length === 0) {\n  // Return special flag so next node can stop the workflow\n  return [{ json: { hasDownPages: false, totalMonitored: allPages.length, downCount: 0, upCount } }];\n}\n\nreturn [{\n  json: {\n    hasDownPages: true,\n    totalMonitored: allPages.length,\n    downCount: downPages.length,\n    upCount,\n    downPages,\n    checkedAt: new Date().toLocaleString('en-AU', { timeZone: 'Australia/Sydney' })\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "1dece255-57b7-4078-a606-ee38db428c14",
      "name": "Any Pages Down?",
      "type": "n8n-nodes-base.if",
      "position": [
        1312,
        -16
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-down",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.hasDownPages }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "26de92a6-f8fc-49d5-a2bf-c392b28d1e29",
      "name": "Compose HTML Email",
      "type": "n8n-nodes-base.code",
      "position": [
        1584,
        -80
      ],
      "parameters": {
        "jsCode": "const data = $input.first().json;\nconst { downPages, totalMonitored, downCount, upCount, checkedAt } = data;\n\n// Build table rows for each down page\nconst rows = downPages.map(page => {\n  const statusLabel = page.status === 'ERROR' ? '\u26a0\ufe0f ERROR' : `\u274c ${page.status}`;\n  const retryNote = page.retried ? '<span style=\"color:#888;font-size:11px\">(confirmed after retry)</span>' : '';\n  return `\n    <tr>\n      <td style=\"padding:10px;border-bottom:1px solid #f0f0f0;word-break:break-all\">\n        <a href=\"${page.URL}\" style=\"color:#e53e3e\">${page.URL}</a>\n      </td>\n      <td style=\"padding:10px;border-bottom:1px solid #f0f0f0;text-align:center;font-weight:bold;color:#e53e3e\">${statusLabel}</td>\n      <td style=\"padding:10px;border-bottom:1px solid #f0f0f0;text-align:center;color:#666\">${page.responseTime}ms ${retryNote}</td>\n    </tr>`;\n}).join('');\n\nconst html = `\n<!DOCTYPE html>\n<html>\n<body style=\"font-family:Arial,sans-serif;background:#f9f9f9;margin:0;padding:20px\">\n  <div style=\"max-width:640px;margin:0 auto;background:#fff;border-radius:8px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.08)\">\n    \n    <!-- Header -->\n    <div style=\"background:#e53e3e;padding:24px 32px\">\n      <h1 style=\"color:#fff;margin:0;font-size:22px\">\ud83d\udea8 Webpage Down Alert</h1>\n      <p style=\"color:#fed7d7;margin:8px 0 0\">Checked at ${checkedAt} (AEST)</p>\n    </div>\n\n    <!-- Summary -->\n    <div style=\"padding:24px 32px;background:#fff5f5;border-bottom:1px solid #fed7d7\">\n      <table style=\"width:100%;border-collapse:collapse\">\n        <tr>\n          <td style=\"text-align:center;padding:12px\">\n            <div style=\"font-size:32px;font-weight:bold;color:#e53e3e\">${downCount}</div>\n            <div style=\"color:#666;font-size:13px\">Pages Down</div>\n          </td>\n          <td style=\"text-align:center;padding:12px\">\n            <div style=\"font-size:32px;font-weight:bold;color:#38a169\">${upCount}</div>\n            <div style=\"color:#666;font-size:13px\">Pages Up</div>\n          </td>\n          <td style=\"text-align:center;padding:12px\">\n            <div style=\"font-size:32px;font-weight:bold;color:#4a5568\">${totalMonitored}</div>\n            <div style=\"color:#666;font-size:13px\">Total Monitored</div>\n          </td>\n        </tr>\n      </table>\n    </div>\n\n    <!-- Table -->\n    <div style=\"padding:24px 32px\">\n      <h2 style=\"font-size:16px;color:#2d3748;margin:0 0 16px\">Down Pages Details</h2>\n      <table style=\"width:100%;border-collapse:collapse;font-size:14px\">\n        <thead>\n          <tr style=\"background:#f7fafc\">\n            <th style=\"padding:10px;text-align:left;color:#4a5568;border-bottom:2px solid #e2e8f0\">URL</th>\n            <th style=\"padding:10px;text-align:center;color:#4a5568;border-bottom:2px solid #e2e8f0\">Status</th>\n            <th style=\"padding:10px;text-align:center;color:#4a5568;border-bottom:2px solid #e2e8f0\">Response Time</th>\n          </tr>\n        </thead>\n        <tbody>${rows}</tbody>\n      </table>\n    </div>\n\n    <!-- Footer -->\n    <div style=\"padding:16px 32px;background:#f7fafc;border-top:1px solid #e2e8f0\">\n      <p style=\"color:#a0aec0;font-size:12px;margin:0\">This alert was sent automatically by your n8n Webpage Monitor. All failed URLs were retried once before alerting.</p>\n    </div>\n  </div>\n</body>\n</html>`;\n\nreturn [{ json: { html, subject: `\ud83d\udea8 ${downCount} Webpage(s) Down \u2014 ${checkedAt}` } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "591794ee-7b72-42c1-a13a-17b9dfcae146",
      "name": "Send Alert Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1792,
        -80
      ],
      "parameters": {
        "sendTo": "user@example.com",
        "message": "={{ $json.html }}",
        "options": {},
        "subject": "={{ $json.subject }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "35ed5b33-ba6d-4477-afd8-97a325e03c09",
      "name": "Sticky Note \u2014 Step ",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1520,
        -192
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 424,
        "content": "## Step 4\nCompose and send email"
      },
      "typeVersion": 1
    },
    {
      "id": "bb6d3dce-b34b-4328-88f2-947e5e3e13c0",
      "name": "All Pages Up - Stop",
      "type": "n8n-nodes-base.noOp",
      "position": [
        1584,
        80
      ],
      "parameters": {},
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "bae47254-fd29-44a8-acd4-96176d8d44ed",
  "connections": {
    "Any Pages Down?": {
      "main": [
        [
          {
            "node": "Compose HTML Email",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "All Pages Up - Stop",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compose HTML Email": {
      "main": [
        [
          {
            "node": "Send Alert Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch All Webpages": {
      "main": [
        [
          {
            "node": "Check All Webpages (with Retry)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Down Webpages": {
      "main": [
        [
          {
            "node": "Any Pages Down?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Runs Every Day at 8am": {
      "main": [
        [
          {
            "node": "Fetch All Webpages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check All Webpages (with Retry)": {
      "main": [
        [
          {
            "node": "Filter Down Webpages",
            "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

Webpage Down Detector automatically monitors your website pages and alerts you if any go down. This workflow saves time, prevents silent downtime, and keeps your website reliable.

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

Automatically extract structured information from emails using AI-powered document analysis. This workflow processes emails from specified domains, classifies them by type, and extracts structured dat

Gmail, HTTP Request, AWS S3 +1
Email & Gmail

This weekly workflow helps you stay on top of SEO visibility losses by automatically detecting when your previously strong keywords fall out of Google’s top 10 results.

N8N Nodes Dataforseo, Google Sheets, Gmail
Email & Gmail

What This Flow Does

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

This n8n workflow sends personalized outreach emails automatically while enforcing strict safety rules such as email validation, spam checks, daily limits, and human-like delays.

Google Drive, Google Sheets, Gmail
Email & Gmail

This workflow automates a 3-step cold email sequence from Gmail using leads in Google Sheets, generates personalized copy with Anthropic Claude, enforces a gradual daily sending cap, schedules follow-

Google Sheets, HTTP Request, Gmail