Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
38ead62
docs: add AI-Powered Gemini Semantic Classifier feature proposal
don-petry Jul 28, 2026
06794e1
docs: update proposal with 7-Domain Standard Taxonomy Matrix & MECE V…
don-petry Jul 28, 2026
fa96d54
docs: add Inbox Preservation rule (processing to Drive does NOT remov…
don-petry Jul 28, 2026
2496323
docs: resolve merge conflict and add Inbox Preservation rule
don-petry Jul 28, 2026
19ae580
docs: use Single Global Processed Label marker to eliminate sidebar l…
don-petry Jul 28, 2026
b78ac1d
docs: detail Idempotency & Single-Click Reprocessing workflows
don-petry Jul 28, 2026
8021d5a
feat: add native Gemini AI classifier and auto-filter engine module
don-petry Jul 28, 2026
cd7e914
chore: dev-lead update (review-changes) [skip ci-relay]
donpetry-bot Jul 28, 2026
034b23c
chore: dev-lead update (review-changes) [skip ci-relay]
donpetry-bot Jul 28, 2026
f1a8dc9
chore: dev-lead update (review-changes) [skip ci-relay]
donpetry-bot Jul 28, 2026
15ee6da
fix(ci): auto-fix for SonarCloud Code Analysis [skip ci-relay]
donpetry-bot Jul 28, 2026
d8154d3
fix(ci): auto-fix for SonarCloud Code Analysis [skip ci-relay]
donpetry-bot Jul 28, 2026
6ffb452
fix(ci): auto-fix for SonarCloud Code Analysis [skip ci-relay]
donpetry-bot Jul 28, 2026
4996a6e
chore: dev-lead update (review-changes) [skip ci-relay]
donpetry-bot Jul 28, 2026
bae0762
fix(ci): auto-fix for SonarCloud Code Analysis [skip ci-relay]
donpetry-bot Jul 28, 2026
f1ae720
fix(reviews): address review comments [skip ci-relay]
donpetry-bot Jul 28, 2026
3e934e5
fix(ci): auto-fix for SonarCloud Code Analysis [skip ci-relay]
donpetry-bot Jul 28, 2026
05db839
fix(ci): auto-fix for SonarCloud Code Analysis [skip ci-relay]
donpetry-bot Jul 28, 2026
ccf9ad5
fix(ci): auto-fix for SonarCloud Code Analysis [skip ci-relay]
donpetry-bot Jul 28, 2026
257460e
fix(ci): auto-fix for SonarCloud Code Analysis [skip ci-relay]
donpetry-bot Jul 28, 2026
995b18a
docs: detail Unclassified & Edge-Case email handling safety nets
don-petry Jul 28, 2026
3fcd9cc
feat: add native Google Apps Script GitHub Sync module (UrlFetchApp -…
don-petry Jul 28, 2026
1d65a71
chore(ci): apply prettier/eslint auto-fixes
github-actions[bot] Jul 28, 2026
c1ab1ba
fix(bot): address bot feedback [skip ci-relay]
donpetry-bot Jul 28, 2026
899ad30
fix(bot): address bot feedback [skip ci-relay]
donpetry-bot Jul 28, 2026
d5f21ee
test: add performance and scalability benchmark test suite for long d…
don-petry Jul 28, 2026
ee1ae05
feat: enhance gitHubSync with targeted insertion and 409 SHA collisio…
don-petry Jul 28, 2026
f06010c
chore: dev-lead update (review-changes) [skip ci-relay]
donpetry-bot Jul 28, 2026
64b46ec
feat: add Option A multi-account user email resolution and deployment…
don-petry Jul 29, 2026
b41cdd1
chore(ci): apply prettier/eslint auto-fixes
github-actions[bot] Jul 29, 2026
48c8a3e
feat: add modelEndpoint to classifier config
donpetry-bot Jul 29, 2026
f1f8aa1
feat: add appsscript.json manifest for clasp deployment
don-petry Jul 29, 2026
581cc10
chore: add .claspignore to exclude Node.js Jest test files from Apps …
don-petry Jul 29, 2026
6f6185d
fix: export getAiClassifierConfig and standalone GAS logic
don-petry Jul 29, 2026
bcdde8f
fix: safe Gemini API HTTP status checking and response guards
don-petry Jul 29, 2026
ea2bf36
fix: update Gemini API endpoint to gemini-2.0-flash with automatic fa…
don-petry Jul 29, 2026
db9dad7
feat: add listAvailableGeminiModels helper and expanded model endpoints
don-petry Jul 29, 2026
3c76f60
feat: set gemini-3.1-flash-lite as 2nd fallback endpoint
don-petry Jul 29, 2026
ea307b3
feat: add 404 auto-create note functionality with Progressive Disclos…
don-petry Jul 29, 2026
e75dc95
feat: reorder endpoints to highest-level model first and add HTTP 429…
don-petry Jul 29, 2026
0cb6756
feat: set Gemini 3.5 Flash Lite as primary endpoint #1
don-petry Jul 29, 2026
184fb59
feat: set Gemini 3.5 Flash Lite as #1 and Gemini 3.1 Flash Lite as #2
don-petry Jul 29, 2026
8ecfaab
chore: remove deprecated 1.x and 2.x Gemini models, use 3.x models only
don-petry Jul 29, 2026
466fad3
chore: dev-lead update (review-changes) [skip ci-relay]
donpetry-bot Jul 29, 2026
683d5a9
feat: add Family/DJ-Rachel sub-label and strict prompt classification…
don-petry Jul 29, 2026
dff51b9
feat: exclude health newsletters and news bulletins from 04_Family_He…
don-petry Jul 29, 2026
b566b95
feat: add setupFiveMinuteTrigger and stopAllTriggers for automated ba…
don-petry Jul 29, 2026
1656f46
feat: map student college/career planning meetings to Family/School
don-petry Jul 29, 2026
d813331
feat: map MyBroodMinder alerts to 07_Community_NonProfit with sub-lab…
don-petry Jul 29, 2026
7037ee1
feat: map ParentSquare and MCAA school emails to Family/School-Toby
don-petry Jul 29, 2026
4befa97
fix: add 2-second rate limit backoff delay on HTTP 429 and stabilize …
don-petry Jul 30, 2026
c0408e0
feat: parse and honor Retry-After header and retryDelay on HTTP 429
don-petry Jul 30, 2026
ba84dfb
chore: strictly enforce Gemini 3.x models ONLY
don-petry Jul 30, 2026
28404ed
feat: add Gemma 4 31B (gemma-4-31b-it) as primary endpoint #1
don-petry Jul 30, 2026
57590f5
fix: add extractJsonSubstring helper to parse Gemma 4 31B markdown fo…
don-petry Jul 30, 2026
a763bd6
feat: exclude tech webinars and commercial store marketing from core …
don-petry Jul 30, 2026
1ab8558
feat: add automated action execution (trash, archive, mark_read) and …
don-petry Jul 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions docs/proposals/proposal-001-ai-gemini-semantic-classifier.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Feature Proposal 001: Native Gemini AI Semantic Email Classifier & 7-Domain Taxonomy Engine

## Executive Summary
This proposal introduces a native **Gemini AI Semantic Email Classifier & Standard Taxonomy Engine** into the `google-app-scripts` suite.

By calling the Gemini REST API (`gemini-1.5-flash` / `gemini-2.0-flash`) natively via Google Apps Script's `UrlFetchApp.fetch()`, incoming unclassified emails are semantically categorized into **7 Standard Canonical Domains**, and high-confidence classifications automatically generate permanent Gmail filter rules via the Advanced Gmail Service.

---

## Idempotency & Reprocessing Workflows

- **Query Guard**: Ingestion routines process threads matching `label:Category -label:Processed`.
- **Deduplication Safety Nets**: MD5 native checksums (`file.getMd5Checksum()`) prevent duplicate attachment saves; thread markers (`[THREAD:id]`) prevent duplicate text logs.
- **Single-Click Reprocessing**: Removing the `Processed` label from an email thread in Gmail causes the script to re-evaluate, re-clean, and re-process the thread on its next run.

---

## Clean Ingestion Marker: Single Global `Processed` Label

- **Eliminating Duplicate Labels**: Rather than creating parallel `-Processed` labels (which doubles sidebar labels from 20 to 40+), the script applies a **single global `Processed` label** (or Green Star badge) to indicate Drive ingestion completion.
- **Category Preservation**: The thread retains its canonical category label (`Family/School-Toby`, `Finance/Taxes`).
- **Inbox Preservation**: Processing an email into Google Drive **MUST NOT remove the message from the user's INBOX**. The email thread remains visible in the Inbox for human review until explicitly archived or deleted by the user.

---

## Standard 7-Domain Taxonomy Matrix (PARA / ISO 15489 / MECE Aligned)

The classifier operates against a standardized, mutually exclusive, collectively exhaustive (MECE) 7-domain taxonomy:

| 7 Canonical Domains | Standard PARA Area | Scope & Contents |
| :--- | :--- | :--- |
| **`01_Household`** | Real Estate & Living Space | Primary residence (2809 Five Oaks), shop build, rental (3535 Broken Bow), 18 Running Deer archive |
| **`02_Finance_Legal`** | Financial & Legal Protections | Taxes, banking (Schwab/BoA), insurance policies, court records, custody, estate planning |
| **`03_Vehicles`** | Transportation & Assets | Vehicle searches (2026 car hunt), vehicle titles, Mercedes logs, auto maintenance |
| **`04_Family_Health`** | Family Welfare & Health | Children (Toby, Tide), school IEPs, speech/audiology reports, medical history, AYOP camps |
| **`05_Tech_Infrastructure`**| IT & Home Automation | Home Assistant, NAS backups, Tasker scripts, network gear, software licenses |
| **`06_Work_Career`** | Professional Life & Income | EY architecture, resumes, job interview decks, expense reports, admin notes |
| **`07_Community_NonProfit`**| Civic Duty & Service | HelpingOneGuy 501(c)(3) charity, JeffCo Bees BOD, faith outreach |
Comment thread
don-petry marked this conversation as resolved.
Resolved

---

## Key Capabilities

1. **Zero-Configuration Semantic Classification**:
- Classifies incoming emails from unknown senders based on semantic intent, sender, subject, and body snippet into target canonical labels (`Family/School`, `Finance/Taxes`, `Household/Rentals`, etc.).
2. **Self-Learning Gmail Filter Generation**:
- When Gemini classifies a new sender with high confidence ($\ge 95\%$), the script automatically calls `Gmail.Users.Settings.Filters.create` to create a permanent static Gmail filter rule. Future emails from that sender are processed instantly by Gmail's fast native filter engine at $0$ cost.
Comment thread
don-petry marked this conversation as resolved.
Resolved
3. **Single-Click Reprocessing**:
- Simply remove the `Processed` label from any thread to re-run and re-ingest.
4. **100% Cloud-Native & Serverless**:
- Executes natively inside Google Apps Script via `UrlFetchApp.fetch()`. Requires zero external servers, Docker containers, or Python infrastructure.

---

## Code Module Design

Target file structure in `google-app-scripts`:

```
src/gmail-ai-classifier/
├── README.md
├── code.gs
├── config.gs
└── tests/
├── code.test.js
└── integration.test.js
Comment thread
don-petry marked this conversation as resolved.
Resolved
```

---

## Discussion & Tracking
- Issue Tracked: [#491](https://github.com/petry-projects/google-app-scripts/issues/491)
- Pull Request Tracked: [#492](https://github.com/petry-projects/google-app-scripts/pull/492)
- Repository: `petry-projects/google-app-scripts`
15 changes: 15 additions & 0 deletions src/gmail-ai-classifier/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Gmail AI Classifier & Auto-Filter Engine

A native Google Apps Script that uses the Gemini REST API (`gemini-1.5-flash` / `gemini-2.0-flash`) to semantically classify incoming Gmail messages into standard canonical domain labels and automatically generate permanent Gmail filter rules for new senders.

## Features

- **Semantic Email Classification**: Evaluates incoming email senders, subjects, and text snippets to route messages into canonical domains.
- **Single Global `Processed` Marker**: Applies a single global `Processed` label to indicate Drive ingestion completion without cluttering the sidebar.
- **Inbox Preservation**: Processing emails into Google Drive preserves message visibility in the INBOX for human review.
- **Self-Learning Filter Creation**: Automatically generates permanent static Gmail filter rules via the Advanced Gmail API when confidence $\ge 95\%$.
- **100% Cloud-Native**: Runs natively inside Google Apps Script using `UrlFetchApp.fetch()`.

## Configuration (`config.gs`)

Set your Gemini API key in Google Apps Script **Project Settings -> Script Properties** with key `GEMINI_API_KEY`.
128 changes: 128 additions & 0 deletions src/gmail-ai-classifier/code.gs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// @version 1.0.0
/**
* Main entry point for AI-Powered Semantic Email Classification and Auto-Filter Generation.
*/
function processEmailsWithAiClassifier() {
console.log('[processEmailsWithAiClassifier] Starting AI semantic email processing');
var config = getAiClassifierConfig();

var threads = GmailApp.search(config.unprocessedQuery, 0, 15);
console.log('[processEmailsWithAiClassifier] Found', threads.length, 'unprocessed threads');

if (threads.length === 0) {
return;
}

var processedLabel = ensureUserLabel(config.processedLabel);

threads.forEach(function (thread, index) {
console.log('[processEmailsWithAiClassifier] Processing thread', index + 1, 'of', threads.length);
var messages = thread.getMessages();
if (!messages || messages.length === 0) return;
Comment thread
don-petry marked this conversation as resolved.
Resolved

var latestMsg = messages[messages.length - 1];
var sender = latestMsg.getFrom();
var subject = latestMsg.getSubject();
var bodySnippet = latestMsg.getPlainBody() ? latestMsg.getPlainBody().substring(0, 1200) : '';

var classification = classifyWithGemini(config, sender, subject, bodySnippet);
if (!classification || !classification.canonical_label) {
console.log('[processEmailsWithAiClassifier] Could not classify thread:', thread.getId());
return;
}

console.log('[processEmailsWithAiClassifier] Classified as:', classification.canonical_label, '(Confidence:', classification.confidence, ')');

// 1. Apply Canonical Category Label (Preserves INBOX visibility)
var categoryLabel = ensureUserLabel(classification.canonical_label);
thread.addLabel(categoryLabel);

// 2. Apply Single Global Processed Marker Label
thread.addLabel(processedLabel);

// 3. Auto-Create Permanent Gmail Filter Rule if Confidence >= Threshold
if (classification.confidence >= config.autoFilterConfidenceThreshold) {
createGmailFilterRule(sender, classification.canonical_label);
}
Comment thread
don-petry marked this conversation as resolved.
Resolved
});

console.log('[processEmailsWithAiClassifier] AI processing batch completed');
}

/**
* Calls Gemini REST API natively via UrlFetchApp
*/
function classifyWithGemini(config, sender, subject, bodyText) {
var url = config.modelEndpoint + '?key=' + config.geminiApiKey;

var prompt = 'You are an executive email classifier.\n' +
'Classify the following email into exactly ONE of these canonical domain labels:\n' +
config.canonicalDomains.join('\n') + '\n\n' +
'Email Details:\n' +
'Sender: ' + sender + '\n' +
'Subject: ' + subject + '\n' +
'Body Snippet: ' + bodyText + '\n\n' +
'Respond ONLY in JSON format matching this schema:\n' +
'{"canonical_label": "Domain/Subfolder", "confidence": 0.98, "reasoning": "short explanation"}';

var payload = {
contents: [{ parts: [{ text: prompt }] }],
generationConfig: { response_mime_type: 'application/json' }
};

var options = {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload),
muteHttpExceptions: true
};

try {
var response = UrlFetchApp.fetch(url, options);
var jsonText = response.getContentText();
var parsed = JSON.parse(jsonText);
var outputText = parsed.candidates[0].content.parts[0].text;
return JSON.parse(outputText);
} catch (e) {
console.error('[classifyWithGemini] Error calling Gemini REST API:', e.message);
return null;
}
}

/**
* Creates permanent static Gmail filter rule via Advanced Gmail Service API
*/
function createGmailFilterRule(senderEmail, labelName) {
try {
var cleanSender = senderEmail.replace(/.*<(.+)>/, '$1').trim();
var targetLabel = ensureUserLabel(labelName);

var filterBody = {
criteria: { from: cleanSender },
action: { addLabelIds: [targetLabel.getId()] }
};
Comment thread
don-petry marked this conversation as resolved.
Resolved

if (typeof Gmail !== 'undefined' && Gmail.Users && Gmail.Users.Settings && Gmail.Users.Settings.Filters) {
Gmail.Users.Settings.Filters.create(filterBody, 'me');
console.log('[createGmailFilterRule] Permanent Gmail filter created for:', cleanSender, '->', labelName);
Comment thread
don-petry marked this conversation as resolved.
Resolved
} else {
console.log('[createGmailFilterRule] Advanced Gmail service not enabled. Auto-filter creation skipped.');
}
} catch (e) {
console.error('[createGmailFilterRule] Error creating filter:', e.message);
}
}
Comment thread
don-petry marked this conversation as resolved.
Resolved

/**
* Helper to ensure a Gmail user label exists
*/
function ensureUserLabel(labelName) {
var existing = GmailApp.getUserLabelByName(labelName);
if (existing) return existing;
try {
return GmailApp.createLabel(labelName);
} catch (e) {
console.error('[ensureUserLabel] Error creating label:', labelName, e.message);
return null;
}
}
51 changes: 51 additions & 0 deletions src/gmail-ai-classifier/config.gs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Configuration for Gmail AI Classifier & Auto-Filter Engine
*/
function getAiClassifierConfig() {
return {
// Gemini API Key (stored in ScriptProperties or set here)
geminiApiKey: PropertiesService.getScriptProperties().getProperty('GEMINI_API_KEY') || 'YOUR_GEMINI_API_KEY',

// Model Endpoint (Gemini 1.5 Flash / Gemini 2.0 Flash)
modelEndpoint: 'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent',

// Trigger Label applied to unprocessed inbox items
unprocessedQuery: 'inbox -label:Processed',

// Single Global Ingestion Marker Label
processedLabel: 'Processed',
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

// Minimum confidence threshold (0.0 - 1.0) required to auto-create permanent Gmail filter rules
autoFilterConfidenceThreshold: 0.95,

// 7 Canonical Domains for Semantic Classification
canonicalDomains: [
'01_Household/Primary_House',
'01_Household/Shop_Build',
'01_Household/Rental_3535_Broken_Bow',
'01_Household/Archive_18_Running_Deer',
'02_Finance_Legal/Taxes',
'02_Finance_Legal/Insurance',
'02_Finance_Legal/Banking',
'02_Finance_Legal/Legal_Court',
'02_Finance_Legal/Estate_Planning',
'03_Vehicles/Car_Hunt',
'03_Vehicles/Mercedes',
'03_Vehicles/Maintenance',
'04_Family_Health/Bowens',
'04_Family_Health/Charley',
'04_Family_Health/David',
'04_Family_Health/DJ',
'04_Family_Health/Medical_Records',
'04_Family_Health/Activities_AYOP_Camps',
'05_Tech_Infrastructure/NAS_Backups',
'05_Tech_Infrastructure/Tasker',
'05_Tech_Infrastructure/Hardware_Licenses',
'06_Work_Career/EY',
'06_Work_Career/Career_Interviews',
'06_Work_Career/Expenses_Admin',
'07_Community_NonProfit/HelpingOneGuy',
'07_Community_NonProfit/JeffCo_Bees'
]
};
}
17 changes: 17 additions & 0 deletions src/gmail-ai-classifier/tests/code.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Unit tests for Gmail AI Classifier
*/

describe('Gmail AI Classifier Config', () => {
test('returns standard config schema', () => {
const config = {
geminiApiKey: 'TEST_KEY',
processedLabel: 'Processed',
autoFilterConfidenceThreshold: 0.95,
canonicalDomains: ['01_Household/Primary_House', '04_Family_Health/Bowens']
}
expect(config.processedLabel).toBe('Processed')
expect(config.autoFilterConfidenceThreshold).toBe(0.95)
expect(config.canonicalDomains).toContain('04_Family_Health/Bowens')
})
})
Comment thread
don-petry marked this conversation as resolved.
Resolved
Loading