Apps Script 90-minute daily trigger runtime: polling Sume jobs

Consumer Google accounts get 90 minutes of trigger runtime a day. Poll Sume jobs from one time-driven trigger that scans the sheet, not one trigger per row.

5 min readSume
All posts

Use one time-driven trigger that scans the sheet for rows with a job id and no terminal status, and checks each with GET /v1/jobs/{id}/status. Google's quota page lists total trigger runtime at 90 minutes a day for consumer accounts and 6 hours a day for Google Workspace accounts, and 20 triggers per user per script, so a trigger per row runs out of both long before a poll loop does.

Quota figures are from Google's Quotas for Google Services (read 2026-10-02); the status endpoint from Sume's Jobs and results. Google notes that quotas can change, so recheck the page before sizing.

Which Apps Script quotas matter for polling?

Three limits bite a poller, and they are separate from the 6-minute single-execution cap and the daily UrlFetch count covered in the UrlFetch post.

Rows from Google's Apps Script quota page, read 2026-10-02.
QuotaConsumer accountGoogle Workspace account
Triggers total runtime90 min / day6 hr / day
Triggers20 / user / script20 / user / script
Script runtime6 min / execution6 min / execution
URL Fetch calls20,000 / day100,000 / day

How much time can each poll run take?

This is arithmetic from the quota, not a measurement. A trigger that fires every 5 minutes runs 288 times a day. Dividing 90 minutes (5,400 seconds) by 288 gives about 18.75 seconds of runtime per run on a consumer account, and 6 hours (21,600 seconds) gives 75 seconds on Workspace. Every UrlFetchApp.fetch call spends that time, so a run that checks 40 open rows one by one needs to stay inside the average or the day's budget runs out before the day does.

Two ways to spend less: poll less often (Sume's status response can carry next_poll_after_seconds, which tells you when checking again is worthwhile), and only fetch rows still open. A finished row costs nothing the next time round.

What does the poll loop look like?

Columns here are A row id, B Sume job id, C status. The key is read from Script Properties, which Google limits to 9 KB per value and 500 KB per store, so keep ids in the sheet rather than in properties.

function pollSumeJobs() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const key = PropertiesService.getScriptProperties().getProperty("SUME_API_KEY");
  const rows = sheet.getDataRange().getValues();
  const done = ["completed", "failed", "canceled"];
  for (let i = 1; i < rows.length; i++) {
    const jobId = rows[i][1];
    if (!jobId || done.indexOf(rows[i][2]) !== -1) continue;
    const res = UrlFetchApp.fetch(
      "https://api.sume.com/v1/jobs/" + jobId + "/status",
      { headers: { Authorization: "Bearer " + key }, muteHttpExceptions: true }
    );
    if (res.getResponseCode() !== 200) continue;
    const s = JSON.parse(res.getContentText());
    if (s.terminal) sheet.getRange(i + 1, 3).setValue(s.sume_status);
  }
}

When is a webhook the better route?

When the poll budget is tight. Sume's mode: "webhook" sends the terminal event to a public HTTPS URL, so no trigger time is spent waiting. An Apps Script web app can receive it, but the doPost handler cannot read request headers, so it cannot check Sume's signature header; the doPost post covers that. Keep this poller as the backup either way, because Sume's docs say delivery is never the only recovery path.

Sources

Related posts

More in Integrations

All Integrations posts

Written by Sume