TL;DR A Google Apps Script runs every morning, grabs the last 24 hours of email, asks Gemini which messages were written by real people who actually expect a reply, and sends me a short report. No keyword guessing: the model reads the content and decides.
My inbox, like everyone’s, is mostly noise: newsletters, platform notifications, receipts, “your invoice is ready”. Buried in there are the few emails written by actual humans who are waiting for an answer, and those are exactly the ones that tend to slip through.
(All addresses and names in this post are placeholders.)
Asking Gemini directly (and why it’s not enough)
If you enable the Google Workspace extension in the Gemini app, you can prepend @Gmail to a request and query your mailbox directly:
@Gmail find the emails I haven't replied to |
It works for one-off queries, but it has two problems: the on-demand search isn’t great at telling a real person from a platform pretending to be one, and — more importantly — you can’t schedule it. There is no “send me this report every morning” option.
So I moved the logic to Google Apps Script, which can run on a daily timer, and delegated the “smart” part to Gemini.
The idea: fuzzy filtering
Instead of trying to guess with keywords or sender filters, the script sends an excerpt of each email to Gemini and lets it decide whether it looks like a human asking for something. The model picks up on context — a personal tone, an explicit request, or conversely an automated notification dressed up as normal text.
The flow:
- Collect emails from the last 24 hours, skipping threads where the last message is mine (already replied).
- Extract a snippet of each body.
- Send everything to Gemini with a prompt that asks for JSON only: the ids of the emails worth replying to, and why.
- Build the report and email it to me.
Step 1 — Get a (free) API key
Go to aistudio.google.com, then Get API Key → Create API Key and copy it.
Step 2 — Store the key in script properties
Create a new project on script.google.com, then go to the gear icon → Project Settings, scroll down to Script Properties and add a property named GEMINI_KEY with your key as the value. This way the key never appears in the code itself.
Step 3 — The code
function sendEmailRecapWithGemini() { const REPORT_RECIPIENT = "[email protected]"; // 1. Emails from the last 24 hours const query = "newer_than:1d"; const threads = GmailApp.search(query); if (threads.length === 0) { Logger.log("No new email in the last 24 hours."); return; } const emailsToAnalyze = []; for (let i = 0; i < threads.length; i++) { const thread = threads[i]; const messages = thread.getMessages(); const lastMessage = messages[messages.length - 1]; // Skip if I already replied (last message in the thread is mine) const sender = lastMessage.getFrom(); if (sender.includes(GmailApp.getAliases()[0]) || sender.includes(Session.getActiveUser().getEmail())) { continue; } // Body excerpt, to stay within token limits const bodyText = lastMessage.getPlainBody().substring(0, 1500); emailsToAnalyze.push({ id: i, sender: sender, subject: lastMessage.getSubject(), excerpt: bodyText, link: thread.getPermalink() }); } if (emailsToAnalyze.length === 0) { Logger.log("No pending emails after preliminary checks."); return; } // 2. The fuzzy prompt const prompt = `Act as an extremely smart personal assistant. Analyze this list of emails received in the last 24 hours. Your only goal is to identify ONLY the emails written by real people (clients, colleagues, partners, friends) that require a reply, an action or my attention. Strictly exclude: - News, update or marketing newsletters. - Automated transactional emails (purchase receipts, booking confirmations, platform notifications, system alerts). - Social notifications or generic messages from commercial bots. For each email you genuinely believe requires a human reply, fill in this JSON schema. Reply EXCLUSIVELY with valid JSON, no text before or after: [ { "id": , "reason": "" } ] Here is the list of emails to analyze: ${JSON.stringify(emailsToAnalyze, null, 2)}`; // 3. Calling Gemini let selectedEmails = []; try { const apiKey = PropertiesService.getScriptProperties().getProperty("GEMINI_KEY"); if (!apiKey) { Logger.log("Error: GEMINI_KEY not found in script properties."); return; } const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${apiKey}`; const payload = { contents: [{ parts: [{ text: prompt }] }], generationConfig: { responseMimeType: "application/json" } // forces clean JSON }; const response = UrlFetchApp.fetch(url, { method: "POST", contentType: "application/json", payload: JSON.stringify(payload), muteHttpExceptions: true }); const jsonResponse = JSON.parse(response.getContentText()); if (jsonResponse.candidates && jsonResponse.candidates[0].content.parts[0].text) { const modelText = jsonResponse.candidates[0].content.parts[0].text; selectedEmails = JSON.parse(modelText); } else { Logger.log("Unexpected response from Gemini: " + response.getContentText()); return; } } catch (error) { Logger.log("Error while calling Gemini: " + error); return; } // 4. The final report if (selectedEmails && selectedEmails.length > 0) { let body = "Hi,\n\nGemini went through the mailbox and isolated these emails from real people waiting for a reply:\n\n"; selectedEmails.forEach((sel, idx) => { const e = emailsToAnalyze.find(x => x.id === sel.id); if (e) { body += `${idx + 1}. From: ${e.sender}\n Subject: ${e.subject}\n Context: ${sel.reason}\n Reply: ${e.link}\n\n`; } }); GmailApp.sendEmail( REPORT_RECIPIENT, `[Smart Report] ${selectedEmails.length} emails to reply to`, body ); Logger.log("Smart report sent."); } else { Logger.log("No human emails to report today."); } } |
Step 4 — Schedule the daily run
To have the script run by itself every morning:
- In the Apps Script editor, click the clock icon (Triggers) in the left sidebar.
- Bottom right, + Add Trigger.
- Function to run:
sendEmailRecapWithGemini. Event source: time-driven. Type: day timer. Time of day: whatever suits you (I use 7–8 AM, so the report is waiting for me at the start of the day). - Save and grant the requested permissions (Gmail access and sending email on your behalf). If you get the “unverified app” warning, click Advanced → Open [project name].
From then on, Google runs the script daily and the report shows up in your inbox.
Notes
- Cost/limits:
gemini-1.5-flashthrough AI Studio has a generous free tier; if you receive a lot of email, keep an eye on the quota. - Privacy: you are sending excerpts of your email to Google’s AI Studio APIs. Something to keep in mind if you deal with sensitive content.
- Time window:
newer_than:1dlooks at the last 24 hours; align it with the trigger time to avoid missing or duplicating messages. - False positives/negatives: this is a reminder, not a ticketing system. Tweak the prompt (exclusions, tone) based on the patterns of your actual mailbox.
- “Already replied” detection relies on the last message in the thread being yours; replies sent outside Gmail won’t be seen.