●NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaply●OMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflows●AGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactions●MEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuously●THROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and region●DEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20●NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaply●OMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflows●AGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactions●MEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuously●THROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and region●DEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20
Google Workspace × Gemini API Automation: Production Notes on 12 Apps Script Patterns
12 Gemini API + Apps Script patterns for Gmail, Docs, Sheets, and Calendar automation—plus the production snags I hit running this across four sites and an app support inbox: swallowed 429s, JSON code fences, the 6-minute cap, and flash-vs-pro routing, with measured numbers.
Google Apps Script (GAS) gives you programmatic access to every Google Workspace service. When you pair its tight integration with Gemini API's language intelligence, you can transform hours of manual, repetitive work into automated pipelines that run on their own—smarter, faster, and more consistently than any human could manage at scale.
This guide walks through 12 production-ready patterns, complete with working code, covering everything from Gmail triage to cross-service weekly reporting pipelines. We also cover the operational design you need to keep these automations running reliably in a real business environment.
Architecture Overview: How GAS Calls Gemini API
Understanding the data flow helps you design stable, secure automations:
GAS Script (Google's servers)
↓ UrlFetchApp.fetch()
Gemini API (generateContent endpoint)
↓ JSON response
GAS Script
↓ Workspace service calls
Gmail / Docs / Sheets / Calendar
Because GAS runs on Google's servers rather than a browser, your API key never gets exposed to end users. All Workspace service access happens server-side, eliminating the OAuth complexity you'd face in a standalone app.
Environment Setup
Every pattern in this guide uses the shared callGemini() function below. Store your API key (obtained from Google AI Studio) in Script Properties—never hardcode it in your source.
// Run this once to verify your API key is configuredfunction setup() { const props = PropertiesService.getScriptProperties(); // Set GEMINI_API_KEY in Project Settings → Script Properties first Logger.log('API Key loaded: ' + (props.getProperty('GEMINI_API_KEY') ? 'OK' : 'MISSING'));}// Shared Gemini API caller — used by all 12 patternsfunction callGemini(prompt, options = {}) { const apiKey = PropertiesService.getScriptProperties().getProperty('GEMINI_API_KEY'); const model = options.model || 'gemini-2.5-pro'; const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`; const payload = { contents: [{ parts: [{ text: prompt }] }], generationConfig: { temperature: options.temperature || 0.7, maxOutputTokens: options.maxTokens || 2048, } }; const response = UrlFetchApp.fetch(url, { method: 'post', contentType: 'application/json', payload: JSON.stringify(payload), muteHttpExceptions: true // Returns JSON even on errors }); const json = JSON.parse(response.getContentText()); if (json.error) throw new Error(`Gemini API Error: ${json.error.message}`); return json.candidates[0].content.parts[0].text;}
Security Note: Always use Script Properties for API keys. Properties are not exposed when you share a script with collaborators.
✦
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
✦Why muteHttpExceptions swallows 429s, and a shared helper that flags retryable failures with a RETRYABLE_ prefix
✦A sanitizer that strips the JSON code fences Gemini returns ~8% of the time, taking parse failures to near zero
✦A formula to size batch rows against the 6-minute cap, plus a flash/pro task split that cut token cost ~40%
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.
Pattern 1: AI-Powered Email Triage and Priority Labeling
Gemini reads your inbox and automatically labels messages by urgency, category, and required action—so you open Gmail already knowing what needs attention first.
function classifyInboxEmails() { // Fetch the 20 most recent unread messages const threads = GmailApp.search('is:unread in:inbox', 0, 20); threads.forEach(thread => { const message = thread.getMessages()[0]; const subject = message.getSubject(); const body = message.getPlainBody().substring(0, 500); const from = message.getFrom(); const prompt = `Analyze this email and respond in JSON format.Subject: ${subject}From: ${from}Body (excerpt): ${body}Response format:{ "priority": "high|medium|low", "category": "customer|internal|newsletter|billing|other", "action": "reply_today|review_this_week|schedule|read_only", "summary": "max 50 chars"}`; try { const result = JSON.parse(callGemini(prompt, { temperature: 0.2 })); const labelName = `AI/${result.priority}`; let label = GmailApp.getUserLabelByName(labelName); if (!label) label = GmailApp.createLabel(labelName); thread.addLabel(label); if (result.priority === 'high') thread.markImportant(); Logger.log(`${subject}: ${result.priority} — ${result.action}`); } catch (e) { Logger.log(`Error classifying "${subject}": ${e.message}`); } Utilities.sleep(1000); // Rate limit protection });}
Set this to run hourly via a time-based trigger. The is:unread in:inbox search query can be tailored to match your workflow.
Pattern 2: Context-Aware Reply Draft Generator
Instead of starting replies from scratch, Gemini reads the full thread and produces a professional draft you can review and send with minor edits.
function draftReplyForEmail(threadId) { const thread = GmailApp.getThreadById(threadId); const messages = thread.getMessages(); const conversationHistory = messages.map(m => `[${m.getFrom()}] ${m.getSubject()}\n${m.getPlainBody().substring(0, 300)}` ).join('\n---\n'); const prompt = `You are a professional business communicator. Write a reply draft for the following email thread.[Thread]${conversationHistory}Requirements:- Concise and professional- Include a clear next step or call to action- Use [SIGNATURE] as a placeholder- Under 200 words`; const draftBody = callGemini(prompt, { temperature: 0.6 }); GmailApp.createDraft( messages[messages.length - 1].getFrom(), `Re: ${thread.getFirstMessageSubject()}`, draftBody ); return draftBody;}
Pattern 3: Weekly Email Digest to Google Docs
Once a week, Gemini summarizes your important emails into a structured digest document—great for staying on top of communication without spending hours in Gmail.
function generateWeeklyEmailDigest() { const oneWeekAgo = new Date(); oneWeekAgo.setDate(oneWeekAgo.getDate() - 7); const query = `after:${Utilities.formatDate(oneWeekAgo, 'UTC', 'yyyy/MM/dd')} is:important`; const threads = GmailApp.search(query, 0, 50); const summaries = threads.map(thread => { const msg = thread.getMessages()[0]; return { date: Utilities.formatDate(msg.getDate(), 'GMT', 'MM/dd'), from: msg.getFrom(), subject: msg.getSubject(), body: msg.getPlainBody().substring(0, 200) }; }); const prompt = `Analyze these ${summaries.length} important emails from the past week and create a digest.[Emails]${JSON.stringify(summaries, null, 2)}Format your response as:## This Week in Email (${summaries.length} messages)### Action Required- ...### Key Topics- ...### Carry-Forward to Next Week- ...`; const digest = callGemini(prompt, { maxTokens: 1024 }); const doc = DocumentApp.create(`Weekly Email Digest ${Utilities.formatDate(new Date(), 'GMT', 'yyyy-MM-dd')}`); doc.getBody().setText(digest); Logger.log('Weekly digest created: ' + doc.getUrl()); return doc.getUrl();}
Google Sheets Automation Patterns
Pattern 4: AI Anomaly Detection with Automatic Cell Annotations
Gemini monitors your spreadsheet data and flags statistical outliers and trend changes directly as cell notes—no dashboard required.
Pull data from multiple sheets, hand it to Gemini, and get an executive-ready report written directly into a Google Doc—complete with email notification.
function generateMonthlyReport() { const ss = SpreadsheetApp.getActiveSpreadsheet(); const salesData = ss.getSheetByName('Revenue').getDataRange().getValues(); const kpiData = ss.getSheetByName('KPIs').getDataRange().getValues(); const prompt = `Create an executive monthly report from this business data.[Revenue Data - Last 3 Months]${salesData.slice(0, 15).map(r => r.join(', ')).join('\n')}[KPIs]${kpiData.slice(0, 10).map(r => r.join(', ')).join('\n')}Requirements:- Executive-readable in under 5 minutes- Reference specific numbers from the data- Clearly separate risks from opportunities- Propose 3 concrete action items for next month- Around 400 words`; const reportContent = callGemini(prompt, { maxTokens: 2048 }); const month = Utilities.formatDate(new Date(), 'GMT', 'MMMM yyyy'); const doc = DocumentApp.create(`${month} Monthly Report (AI-Generated)`); const body = doc.getBody(); body.appendParagraph(`${month} Monthly Report`).setHeading(DocumentApp.ParagraphHeading.HEADING1); body.appendParagraph(`Generated: ${new Date().toLocaleString()}`); body.appendParagraph(''); body.appendParagraph(reportContent); MailApp.sendEmail({ to: Session.getActiveUser().getEmail(), subject: `[Auto-Generated] ${month} Monthly Report Ready`, body: `Your Gemini AI monthly report has been created.\n\nReport URL: ${doc.getUrl()}` }); return doc.getUrl();}
Pattern 6: Bulk Customer Feedback Analysis and Tagging
Process a backlog of customer feedback rows—sentiment scoring, category tagging, urgency rating, and response guidance—in one automated sweep.
function analyzeCustomerFeedback() { const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Feedback'); const lastRow = sheet.getLastRow(); for (let i = 2; i <= Math.min(lastRow, 51); i++) { // Max 50 rows per run const tagCell = sheet.getRange(i, 5); // Column E = Tag if (tagCell.getValue()) continue; // Skip already-processed rows const content = sheet.getRange(i, 3).getValue(); // Column C = feedback if (!content) continue; const prompt = `Analyze this customer feedback.[Feedback]${content}Respond in JSON:{ "category": "bug|feature_request|complaint|praise|question|other", "sentiment": "positive|neutral|negative", "urgency": "high|medium|low", "tags": ["tag1", "tag2"], "suggestedResponse": "max 80 chars response guidance"}`; try { const result = JSON.parse(callGemini(prompt, { temperature: 0.2 })); sheet.getRange(i, 5).setValue(result.category); sheet.getRange(i, 6).setValue(result.sentiment); sheet.getRange(i, 7).setValue(result.urgency); sheet.getRange(i, 8).setValue(result.tags.join(', ')); sheet.getRange(i, 9).setValue(result.suggestedResponse); const color = result.sentiment === 'negative' ? '#ffcdd2' : result.sentiment === 'positive' ? '#c8e6c9' : '#ffffff'; sheet.getRange(i, 1, 1, 9).setBackground(color); } catch (e) { sheet.getRange(i, 5).setValue('Analysis Error'); Logger.log(`Row ${i} error: ${e.message}`); } Utilities.sleep(1500); } SpreadsheetApp.flush();}
Google Docs Automation Patterns
Pattern 7: Meeting Notes AI Formatter
Paste raw meeting notes into a Doc, run this function, and get back a structured document with clear sections, decisions, and action items—ready to share.
function formatMeetingNotes(docId) { const doc = DocumentApp.openById(docId); const rawText = doc.getBody().getText(); const prompt = `Reformat these raw meeting notes into a clean, structured document.[Raw Notes]${rawText}Output format (plain text, no Markdown tables):## Meeting Overview- Date: - Attendees: - Purpose: ## Decisions Made- ## Action Items- Owner: Task (Due: date)## Next Meeting- Date: - Agenda items: ## Discussion Notes(Key discussion points as bullet list)`; const formatted = callGemini(prompt, { maxTokens: 2048 }); const body = doc.getBody(); body.clear(); formatted.split('\n').forEach(line => { if (line.startsWith('## ')) { body.appendParagraph(line.replace('## ', '')) .setHeading(DocumentApp.ParagraphHeading.HEADING2); } else if (line.startsWith('### ')) { body.appendParagraph(line.replace('### ', '')) .setHeading(DocumentApp.ParagraphHeading.HEADING3); } else if (line.trim()) { body.appendParagraph(line); } }); Logger.log('Meeting notes formatted: ' + doc.getUrl());}
Pattern 8: AI Document Quality Review
Gemini reviews your proposals, specs, or reports and appends detailed feedback—scoring clarity, completeness, and persuasiveness—right at the end of the document.
function reviewDocument(docId, reviewType = 'proposal') { const doc = DocumentApp.openById(docId); const content = doc.getBody().getText(); const reviewCriteria = { proposal: 'persuasiveness, specificity, and risk coverage', spec: 'clarity, implementability, and completeness', report: 'fact/opinion separation and strength of conclusions' }; const prompt = `Review this document for ${reviewCriteria[reviewType]}.[Document]${content.substring(0, 3000)}Respond in JSON:{ "overallScore": 1-10, "strengths": ["strength1", "strength2", "strength3"], "improvements": [ {"section": "section name", "issue": "the problem", "suggestion": "how to fix it"} ], "summary": "max 100 chars overall assessment"}`; const review = JSON.parse(callGemini(prompt, { temperature: 0.3 })); const body = doc.getBody(); body.appendHorizontalRule(); body.appendParagraph('AI Review Results').setHeading(DocumentApp.ParagraphHeading.HEADING2); body.appendParagraph(`Overall Score: ${review.overallScore}/10`); body.appendParagraph(`Summary: ${review.summary}`); body.appendParagraph('Strengths').setHeading(DocumentApp.ParagraphHeading.HEADING3); review.strengths.forEach(s => body.appendListItem(s)); body.appendParagraph('Improvement Suggestions').setHeading(DocumentApp.ParagraphHeading.HEADING3); review.improvements.forEach(item => { body.appendListItem(`[${item.section}] ${item.issue} → ${item.suggestion}`); });}
Google Calendar Automation Patterns
Pattern 9: Automated Meeting Agenda Generator
Each morning, Gemini reads the next day's calendar events and creates tailored agendas based on attendees and meeting purpose—appended directly to each event's description.
function createMeetingAgendas() { const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setHours(0, 0, 0, 0); const dayEnd = new Date(tomorrow); dayEnd.setHours(23, 59, 59, 999); const events = CalendarApp.getDefaultCalendar().getEvents(tomorrow, dayEnd); events.forEach(event => { if (event.getDescription().includes('[Agenda Generated]')) return; const guests = event.getGuestList().map(g => g.getEmail()).join(', '); const title = event.getTitle(); const duration = (event.getEndTime() - event.getStartTime()) / 60000; const prompt = `Create a meeting agenda for this event.Meeting: ${title}Attendees: ${guests || 'unknown'}Duration: ${duration} minutesExisting description: ${event.getDescription()}Format (no markdown tables):Opening (2 min)1. [Topic] (X min) - Objective: - Materials: 2. [Topic] (X min)...Wrap-up & Next Steps (5 min)Ensure total time equals ${duration} minutes.`; const agenda = callGemini(prompt, { temperature: 0.6 }); const updatedDescription = `${event.getDescription()}\n\n[AI Agenda]\n${agenda}\n\n[Agenda Generated]`; event.setDescription(updatedDescription); Utilities.sleep(1000); });}
Pattern 10: Post-Meeting Action Item Email Distribution
Thirty minutes after a meeting ends, Gemini extracts action items from the event notes and emails each assignee their specific tasks automatically.
function sendActionItemsAfterMeeting() { const now = new Date(); const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000); const events = CalendarApp.getDefaultCalendar().getEvents(oneHourAgo, now); events.forEach(event => { if (event.getDescription().includes('[Actions Sent]')) return; const description = event.getDescription(); const guests = event.getGuestList().map(g => g.getEmail()); const prompt = `Extract action items from these meeting notes and assign them to attendees.Meeting: ${event.getTitle()}Notes: ${description}Attendees: ${guests.join(', ')}Respond in JSON:{ "actionItems": [ { "assignee": "email address", "task": "specific task description", "deadline": "YYYY-MM-DD", "priority": "high|medium|low" } ], "summary": "one-sentence meeting summary"}`; try { const result = JSON.parse(callGemini(prompt, { temperature: 0.2 })); const grouped = {}; result.actionItems.forEach(item => { if (!grouped[item.assignee]) grouped[item.assignee] = []; grouped[item.assignee].push(item); }); Object.entries(grouped).forEach(([email, items]) => { const taskList = items.map(t => `• ${t.task} (Due: ${t.deadline} / Priority: ${t.priority})` ).join('\n'); MailApp.sendEmail({ to: email, subject: `[Action Items] ${event.getTitle()}`, body: `Hi,\n\nHere are your action items from ${event.getTitle()}:\n\n${taskList}\n\nMeeting summary: ${result.summary}` }); }); event.setDescription(description + '\n\n[Actions Sent]'); } catch (e) { Logger.log(`Error processing "${event.getTitle()}": ${e.message}`); } });}
Cross-Service Integration Patterns
Pattern 11: Weekly Business Report Pipeline
This pipeline pulls data from Gmail, Sheets, and Calendar, synthesizes everything with Gemini, and delivers a comprehensive weekly report to your inbox—fully automated.
function generateWeeklyBusinessReport() { const oneWeekAgo = new Date(); oneWeekAgo.setDate(oneWeekAgo.getDate() - 7); // 1. Collect important emails const importantEmails = GmailApp.search('is:important newer_than:7d', 0, 20) .map(t => ({ subject: t.getFirstMessageSubject(), from: t.getMessages()[0].getFrom() })); // 2. Fetch KPI data from Sheets const ss = SpreadsheetApp.openById('YOUR_SPREADSHEET_ID'); const kpiData = ss.getSheetByName('Weekly KPIs').getLastRow() > 1 ? ss.getSheetByName('Weekly KPIs').getRange(2, 1, 7, 5).getValues() : []; // 3. Get completed meetings from Calendar const completedMeetings = CalendarApp.getDefaultCalendar() .getEvents(oneWeekAgo, new Date()) .map(e => e.getTitle()); // 4. Synthesize with Gemini const prompt = `Analyze this week's business data and write an executive summary.[Important Emails (${importantEmails.length})]${importantEmails.map(e => `${e.from}: ${e.subject}`).join('\n')}[Weekly KPIs]${kpiData.map(r => r.join(', ')).join('\n')}[Completed Meetings (${completedMeetings.length})]${completedMeetings.join(', ')}Requirements:- Clear summary of this week's wins and challenges- 3 priorities for next week- Flag any risks that need immediate attention- Under 300 words`; const report = callGemini(prompt, { maxTokens: 1500 }); // 5. Save to Docs const week = Utilities.formatDate(new Date(), 'GMT', 'MM/dd'); const doc = DocumentApp.create(`Weekly Business Report — Week of ${week}`); doc.getBody().setText(report); // 6. Email delivery MailApp.sendEmail({ to: Session.getActiveUser().getEmail(), subject: `[Weekly Report] Business Summary — Week of ${week}`, body: `${report}\n\nFull report: ${doc.getUrl()}` }); Logger.log('Weekly report generated');}
Pattern 12: Real-Time Customer Feedback Triage System
Connect this to a Google Form trigger, and every new submission gets analyzed by Gemini instantly—escalating urgent issues and sending automated acknowledgment replies.
function onFormSubmit(e) { const response = e.response; const itemResponses = response.getItemResponses(); const feedbackData = {}; itemResponses.forEach(item => { feedbackData[item.getItem().getTitle()] = item.getResponse(); }); const prompt = `Analyze this customer feedback submission.[Feedback]${JSON.stringify(feedbackData, null, 2)}Respond in JSON:{ "sentiment": "positive|neutral|negative", "urgency": "immediate|within_24h|this_week|no_action", "category": "bug|feature_request|complaint|praise|question", "summary": "max 60 chars", "suggestedAction": "response guidance in max 80 chars", "escalationNeeded": true|false}`; const analysis = JSON.parse(callGemini(prompt, { temperature: 0.1 })); // Log to Sheets const logSheet = SpreadsheetApp.openById('YOUR_SPREADSHEET_ID') .getSheetByName('Feedback Log'); logSheet.appendRow([ new Date(), feedbackData['Email Address'] || 'anonymous', analysis.sentiment, analysis.urgency, analysis.category, analysis.summary, analysis.suggestedAction, analysis.escalationNeeded ? 'ESCALATE' : 'OK' ]); // Escalate high-priority items if (analysis.escalationNeeded || analysis.urgency === 'immediate') { MailApp.sendEmail({ to: 'support-manager@example.com', subject: `🚨 [Urgent] ${analysis.category}: ${analysis.summary}`, body: `Immediate attention needed.\n\nRecommended action: ${analysis.suggestedAction}\n\nFeedback details:\n${JSON.stringify(feedbackData, null, 2)}` }); } // Auto-acknowledge the submitter if (feedbackData['Email Address']) { const responseTime = analysis.urgency === 'immediate' ? 'today' : 'within 5 business days'; MailApp.sendEmail({ to: feedbackData['Email Address'], subject: 'Thank you for your feedback', body: `We've received your feedback and our team will follow up ${responseTime}. We appreciate you taking the time to reach out.` }); }}
For Gmail automation security patterns, our Gmail AI productivity guide covers additional best practices for handling sensitive email data.
What the Docs Don't Tell You: Lessons From Running This in Production
Every pattern above is buildable from the official docs. But once I started running GAS × Gemini to support the operations of Dolice Labs—four sites that publish 16 articles a day between them—and to triage support inquiries for an app portfolio with 50M+ cumulative downloads, I hit a series of snags that no single doc page warns you about. Here are the ones that cost me the most time.
Setting muteHttpExceptions: true in the shared callGemini helper is convenient: GAS returns the body instead of throwing on HTTP errors. The catch is that rate-limit responses (429) come back looking like a "normal" response, so a bare json.error check isn't enough. In my hourly batch of 50 items, roughly 6% (about 3 items) hit 429 during peak hours—but for a while I only noticed it as "classification occasionally goes missing." Reading the status code explicitly is what finally surfaced the cause.
function callGemini(prompt, options = {}) { const apiKey = PropertiesService.getScriptProperties().getProperty('GEMINI_API_KEY'); const model = options.model || 'gemini-2.5-flash'; const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`; const payload = { contents: [{ parts: [{ text: prompt }] }], generationConfig: { temperature: options.temperature ?? 0.7, maxOutputTokens: options.maxTokens || 2048 } }; const response = UrlFetchApp.fetch(url, { method: 'post', contentType: 'application/json', payload: JSON.stringify(payload), muteHttpExceptions: true }); const code = response.getResponseCode(); // ← always inspect the status if (code === 429 || code === 503) { throw new Error(`RETRYABLE_${code}`); // mark as backoff-eligible } const json = JSON.parse(response.getContentText()); if (json.error) throw new Error(`Gemini API Error: ${json.error.message}`); return json.candidates[0].content.parts[0].text;}
Tagging retryable failures with a RETRYABLE_ prefix makes the decision logic inside callGeminiWithRetry (below) noticeably cleaner—it can distinguish "wait and it heals" from "waiting won't help."
2. Gemini often wraps JSON in a code fence
Even when you ask for JSON, Gemini will sometimes return text wrapped in ```json … ```. JSON.parse() chokes on that and throws a SyntaxError. In my measurements, even at temperature: 0.2, about 8% of responses (roughly 80 out of 1,000 in an article-classification task) came back fenced. Unlike the SDKs where you can set responseMimeType, the raw HTTP call from GAS needs its own guard. Dropping in this one sanitizer brought parse failures to near zero.
function parseGeminiJson(text) { // Strip code fences and surrounding prose, then take the first JSON object const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/); const raw = fenced ? fenced[1] : text; const start = raw.indexOf('{'); const end = raw.lastIndexOf('}'); if (start === -1 || end === -1) throw new Error('No JSON object found'); return JSON.parse(raw.slice(start, end + 1));}
I'd replace every JSON.parse(callGemini(...)) in Patterns 1, 4, 6, 10, and 12 with parseGeminiJson(callGemini(...)).
3. Utilities.sleep() eats into your 6-minute budget
Sleeping for rate limits is necessary, but it's easy to forget those sleeps are spent against the 6-minute execution cap. In Pattern 6 (feedback analysis), adding Utilities.sleep(1500) across 50 rows burns 75 seconds in sleep alone; combined with API latency (1.2–2.0s measured per call), you're looking at ~150 seconds. I originally tried to process 80 rows in one run and hit the limit mid-batch. The safe move is to size each trigger so that (sleep + avg latency) × rows < 240s. Filling that in with my measured latency, 40–45 rows was the ceiling per batch; the rest carries over via the checkpoint approach (lastProcessedRow).
4. Using gemini-2.5-pro for every pattern isn't worth it
I first built everything on gemini-2.5-pro. For short, structured-output tasks like email triage (Pattern 1) or sentiment scoring (Pattern 6), gemini-2.5-flash gave near-identical accuracy and felt 2–3× faster. My setup settled into: flash for classification/extraction, and pro reserved only for reasoning-heavy output like report generation (Patterns 5 and 11) or document review (Pattern 8). That split alone cut my monthly token cost by roughly 40%. Designing each pattern to pass options.model explicitly makes those swaps painless later.
Looking back
These 12 patterns demonstrate that the real power of Gemini API in a business context isn't any single automation—it's the ability to chain Gmail, Sheets, Docs, and Calendar together into intelligent workflows that reflect how work actually happens.
Start with the pattern that addresses your biggest pain point today, verify it runs reliably, then layer in additional patterns. The cross-service pipelines in Patterns 11 and 12 become dramatically more valuable once the individual service patterns (1–10) are running smoothly.
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.