Monitor website uptime and response time
Ping Northwind URLs every 10 minutes and alert on failures — DIY uptime monitor.
Published Sep 1, 2025
A paid uptime monitor is the obvious answer when a site goes down, but for a small studio it is one more subscription and one more dashboard to log into. Northwind only runs two URLs — the marketing site and the app — and just wants to know, fast, when either stops responding.
This script does exactly that and nothing more. Every 10 minutes it fetches each URL, records the status code and how long the request took, and emails the team the moment something returns an error. The log of response times also doubles as a rough performance history you can skim when the app feels slow.
What you’ll need
- A Google Sheet to act as the log. The script writes four columns — timestamp, URL, status code, response time in milliseconds — so an empty sheet with no headers is fine; add a header row yourself if you like.
- The spreadsheet’s ID, taken from its URL, dropped into
UPTIME_SHEET_ID. - The list of URLs you want watched, in
SITES. - An address to send alerts to, in
ALERT_RECIPIENT. The account running the script must be able to send mail as itself.
The script
// The spreadsheet that stores the uptime log.
const UPTIME_SHEET_ID = '1abcUptimeId';
// The URLs to check. Add or remove entries freely.
const SITES = ['https://northwind.studio', 'https://app.northwind.studio'];
// Where failure alerts are sent.
const ALERT_RECIPIENT = '[email protected]';
// Any HTTP status at or above this counts as a failure.
const ERROR_STATUS = 400;
/**
* Fetches every site in SITES, logs the result, and emails an alert
* if any site returns an error or fails to respond.
*/
function checkSites() {
const sheet = SpreadsheetApp.openById(UPTIME_SHEET_ID).getSheets()[0];
// Collect a human-readable line for every site that fails this run.
const failures = [];
for (const url of SITES) {
// 1. Time the request so we capture response time, not just status.
const start = Date.now();
try {
// 2. muteHttpExceptions lets us read 4xx/5xx responses instead
// of throwing — a 503 is data, not a crash.
const res = UrlFetchApp.fetch(url, { muteHttpExceptions: true });
const ms = Date.now() - start;
const code = res.getResponseCode();
// 3. Log the timestamp, URL, status code and response time.
sheet.appendRow([new Date(), url, code, ms]);
// 4. Anything 400+ is a failure worth alerting on.
if (code >= ERROR_STATUS) failures.push(url + ' → ' + code);
} catch (e) {
// 5. A thrown error means the request never completed — DNS
// failure, timeout, connection refused. Log and alert on it.
sheet.appendRow([new Date(), url, 'error', e.message]);
failures.push(url + ' → ' + e.message);
}
}
// 6. One email per run, listing every site that failed.
if (failures.length) {
GmailApp.sendEmail(
ALERT_RECIPIENT,
'Site(s) down',
failures.join('\n')
);
}
}
How it works
checkSitesopens the log spreadsheet and grabs its first tab.- For each URL in
SITES, it records the start time so it can measure how long the request takes. - It fetches the URL with
muteHttpExceptions: true, which means a 404 or 503 comes back as a normal response object instead of throwing — that lets the script read and log the status code rather than crashing. - It appends a row with the timestamp, URL, status code and response time in milliseconds.
- Any status code of 400 or above is added to the
failureslist. If the fetch throws entirely — a DNS failure, a timeout, a refused connection — thecatchblock logserrorwith the message and adds that tofailurestoo. - After all sites are checked, if
failuresis not empty it sends a single email listing every site that went wrong this run.
Example run
On a healthy run with both sites up, the log gains two rows and no email is sent:
| Timestamp | URL | Status | Response (ms) |
|---|---|---|---|
| 2026-05-24 14:00 | https://northwind.studio | 200 | 312 |
| 2026-05-24 14:00 | https://app.northwind.studio | 200 | 488 |
If the app returns a server error on the next run, the log records it and the team gets an email:
| Timestamp | URL | Status | Response (ms) |
|---|---|---|---|
| 2026-05-24 14:10 | https://northwind.studio | 200 | 305 |
| 2026-05-24 14:10 | https://app.northwind.studio | 503 | 121 |
Subject: Site(s) down Body: https://app.northwind.studio → 503
Trigger it
This automation only earns its keep on a schedule:
- In the Apps Script editor, open Triggers (the clock icon).
- Click Add Trigger.
- Choose
checkSites, event source Time-driven, type Minutes timer, interval Every 10 minutes. - Save and approve the authorisation prompt.
Ten minutes is a sensible default — frequent enough to catch real outages, infrequent enough to stay well inside quota.
Watch out for
- A 10-minute interval means up to 144 runs a day, each making one fetch per
site. With two sites that is roughly 288 fetches daily — comfortably under
the 20,000-per-day
UrlFetchApplimit, but watch it if you add many URLs. - A passing status code is not a passing page. A site can return 200 while serving a broken page or a maintenance notice. For deeper checks, fetch the body and search it for a string you expect to see.
- One blip should not always page the team. If brief slowdowns cause noise, only alert when a site fails two runs in a row by checking the last logged status before sending.
- The log grows by a few rows every run. Trim or archive it periodically, or point new rows at a fresh tab each month, so the sheet stays quick to open.
- Response time includes Google’s own network latency to your server, so treat the millisecond figures as a relative trend, not an exact benchmark.
Related
Handle streaming responses from an LLM API
Manage long Northwind AI outputs reliably — note: Apps Script UrlFetch is synchronous.
Updated Jan 3, 2026
Cache API responses to cut quota usage
Store and reuse Northwind API responses intelligently — sub-second hits, fewer bills.
Updated Dec 26, 2025
Build an API-key vault and rotation system
Manage Northwind credentials securely at scale — centralised storage, scheduled rotation.
Updated Dec 22, 2025
Build a rate-limit-aware API client
Back off and retry gracefully on 429s — Northwind's robust outbound HTTP pattern.
Updated Dec 14, 2025
Build a generic paginated-API fetcher
Handle cursors and pages for any large dataset — Northwind's standard pull pattern.
Updated Dec 6, 2025