◉GEMINI LABJP
●3.8 FLASH — 3.7 Flash was deprecated on Oct 8. Requests to the old model are routed to 3.8 Flash automatically●NANO 2.1 — gemini-nano-banana-2.1 is GA. gemini-3.1-flash-image is deprecated with no shutdown date yet●DEADLINE — veo-3.1 previews and gemini-omni-flash-preview shut down Oct 22; the Deep Research agent follows Oct 23 (12-13 days left)●Q&A — Users report Gemini CLI marks a subagent run as a success even after it stops at MAX_TURNS●TTS/LIVE — 2.5-era audio and TTS previews shut down Nov 17, 38 days left●NEW — Deprecated with no shutdown date? Re-pick your image-model target with three conditions●3.8 FLASH — 3.7 Flash was deprecated on Oct 8. Requests to the old model are routed to 3.8 Flash automatically●NANO 2.1 — gemini-nano-banana-2.1 is GA. gemini-3.1-flash-image is deprecated with no shutdown date yet●DEADLINE — veo-3.1 previews and gemini-omni-flash-preview shut down Oct 22; the Deep Research agent follows Oct 23 (12-13 days left)●Q&A — Users report Gemini CLI marks a subagent run as a success even after it stops at MAX_TURNS●TTS/LIVE — 2.5-era audio and TTS previews shut down Nov 17, 38 days left●NEW — Deprecated with no shutdown date? Re-pick your image-model target with three conditions
Articles/Workspace
◧ Workspace/2026-06-29Advanced

When Apps Script Time-Driven Triggers Quietly Run Out: Consolidating Gemini Automations into One Dispatcher

Apps Script caps you at 20 triggers per user and a daily total trigger-runtime budget. Here is how to stop your Gemini automations from silently dying as you add more, using a single dispatcher, a schedule table, and an external heartbeat.

gemini117apps-script10google-workspace7automation54trigger

✦ Premium Article

"The auto-reply that worked yesterday had stopped — with no error at all." If you run several Google Workspace automations as an indie developer, you meet this silent failure sooner or later. In my own setup I was generating review-reply drafts for my wallpaper apps, summarizing inquiry emails, and rolling up sales figures — each on its own time-driven trigger. One day they all stopped together, and the execution log did not even record a failure.

The cause was not a bug in my code. I had exhausted Apps Script's trigger resources. We tend to design as if triggers are unlimited, but the ceiling arrives much earlier than expected. Below I pin that ceiling down in numbers, then show how to fold a sprawl of per-job triggers into a single dispatcher that fires every five minutes.

Triggers run dry against two separate limits

What most people miss is that there is more than one constraint. Exhaustion happens against two distinct resources.

The first is the number of triggers. Apps Script allows up to 20 triggers per user, per script. If you grow your automations as "one feature, one trigger," the 20th brings This script has too many triggers, and every ScriptApp.newTrigger after that fails quietly.

The second is the total trigger runtime per day. On a free gmail.com account, cumulative trigger runtime tops out around 90 minutes per day; even a paid Google Workspace account caps at roughly six hours. Because each Gemini API call spends seconds waiting on the network, that time budget melts faster than you would think as jobs pile up. Once it is gone, the rest of the day's triggers simply stop firing, with no notification.

The table below lists the key limits to keep in mind. Note that the numbers depend on the account type.

ResourceFree (gmail.com)Workspace (paid)Symptom on exhaustion
Triggers / user / script2020newTrigger fails
Total trigger runtime / day~90 min~6 hoursFiring stops silently
Max runtime per execution6 min30 minKilled mid-run
UrlFetch calls / day20,000100,000fetch throws

The point that bites is this: a trigger-per-feature design presses on both limits at once. It consumes trigger slots, and the startup overhead and empty checks of each trigger eat into the runtime budget. When mine stopped, I was still under 20 triggers — but three triggers firing every five minutes had drained the daily runtime budget before noon.

Drop one-trigger-per-job and consolidate into a single dispatcher

The skeleton of the fix is simple. Create exactly one trigger and fire it every five minutes. That single entry point decides, from a schedule table, which jobs are due, and runs only those in order. The trigger count stays at one, and so does the runtime footprint.

Start with the job definitions. Each job carries only an interval (in minutes) and the function to run.

// Job definitions. Each is just a declaration: run fn once every intervalMin.
const JOBS = [
  { id: 'reviewReplyDraft', intervalMin: 15, fn: runReviewReplyDraft },
  { id: 'inquirySummary',   intervalMin: 30, fn: runInquirySummary },
  { id: 'salesRollup',      intervalMin: 60, fn: runSalesRollup },
];
 
// The single entry point called by the 5-minute trigger
function dispatch() {
  const lock = LockService.getScriptLock();
  // Avoid overlap if the previous run is still going. Bow out silently if we can't get it.
  if (!lock.tryLock(1000)) return;
  try {
    const props = PropertiesService.getScriptProperties();
    const now = Date.now();
    const runStart = now;
    const RUN_BUDGET_MS = 4 * 60 * 1000; // Safety valve: exit before the 6-min cap
 
    for (const job of JOBS) {
      if (Date.now() - runStart > RUN_BUDGET_MS) break; // Respect the time budget
      const lastKey = 'last_' + job.id;
      const last = Number(props.getProperty(lastKey) || 0);
      if (now - last < job.intervalMin * 60 * 1000) continue; // Not due yet
      try {
        job.fn();
        props.setProperty(lastKey, String(now)); // Advance only on success
      } catch (e) {
        console.error('job failed: ' + job.id + ' / ' + e);
        // On failure, do not update last = it retries next cycle (each fn stays idempotent)
      }
    }
    props.setProperty('heartbeat', String(now)); // Liveness record
  } finally {
    lock.releaseLock();
  }
}

Three things matter here. First, RUN_BUDGET_MS makes the loop exit before hitting the six-minute execution cap; any leftover jobs are picked up by the next dispatch five minutes later. Second, LockService prevents double execution — if a previous run overruns into the next firing, one of them quietly steps aside, so the same job never runs in parallel. Third, last_ is advanced only on success; on failure the timestamp does not move, so the job retries on the next cycle.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦You can now explain why triggers silently stop as you add Gemini automations, in terms of two concrete numbers: the 20-trigger cap and the ~90-minute daily runtime budget
✦You get working code that replaces one-trigger-per-job with a single 5-minute dispatcher driven by a schedule table
✦You can implement a heartbeat plus external monitoring and LockService double-run protection so a stalled pipeline never goes unnoticed
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $15 for lifetime access
View Membership →

Related Articles

◧ Workspace2026-09-08
Four New Steps Landed in Workspace Studio, and I Am Opening the Reply Ones a Week Later
Workspace Studio gained Drive copy and move steps plus Chat and email reply steps. Using the rollout dates, where admin controls arrive before the features, here is how I separate the reversible actions from the ones I cannot take back.
◧ Workspace2026-07-16
I Put =GEMINI() in 420 Cells. The Calls Kept Coming on Days I Never Opened the Sheet
A custom function calling Gemini from Sheets looks best the moment it starts working, and it does not grow from there. Here is what the 30-second ceiling, uncontrolled recalculation, and read-only cells actually cost, measured, plus the code to move to a write-back design.
◧ Workspace2026-05-27
Replying to 11 Languages of Google Play Reviews With Gemini Without Sounding Like a Bot
After two major Android updates, I had to clear a backlog of unanswered Google Play reviews in Japanese, English, Traditional Chinese, Korean, Thai, Italian, Russian, Persian, Ukrainian, and Polish. This is the operations design I settled on for using Gemini as a translation and vocabulary partner without losing the human temperature.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links