Repository navigation
Feature Proposal: AI-Powered Gemini Semantic Email Classifier #492
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 31 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 06794e1
docs: update proposal with 7-Domain Standard Taxonomy Matrix & MECE V…
don-petry fa96d54
docs: add Inbox Preservation rule (processing to Drive does NOT remov…
don-petry 2496323
docs: resolve merge conflict and add Inbox Preservation rule
don-petry 19ae580
docs: use Single Global Processed Label marker to eliminate sidebar l…
don-petry b78ac1d
docs: detail Idempotency & Single-Click Reprocessing workflows
don-petry 8021d5a
feat: add native Gemini AI classifier and auto-filter engine module
don-petry cd7e914
chore: dev-lead update (review-changes) [skip ci-relay]
donpetry-bot 034b23c
chore: dev-lead update (review-changes) [skip ci-relay]
donpetry-bot f1a8dc9
chore: dev-lead update (review-changes) [skip ci-relay]
donpetry-bot 15ee6da
fix(ci): auto-fix for SonarCloud Code Analysis [skip ci-relay]
donpetry-bot d8154d3
fix(ci): auto-fix for SonarCloud Code Analysis [skip ci-relay]
donpetry-bot 6ffb452
fix(ci): auto-fix for SonarCloud Code Analysis [skip ci-relay]
donpetry-bot 4996a6e
chore: dev-lead update (review-changes) [skip ci-relay]
donpetry-bot bae0762
fix(ci): auto-fix for SonarCloud Code Analysis [skip ci-relay]
donpetry-bot f1ae720
fix(reviews): address review comments [skip ci-relay]
donpetry-bot 3e934e5
fix(ci): auto-fix for SonarCloud Code Analysis [skip ci-relay]
donpetry-bot 05db839
fix(ci): auto-fix for SonarCloud Code Analysis [skip ci-relay]
donpetry-bot ccf9ad5
fix(ci): auto-fix for SonarCloud Code Analysis [skip ci-relay]
donpetry-bot 257460e
fix(ci): auto-fix for SonarCloud Code Analysis [skip ci-relay]
donpetry-bot 995b18a
docs: detail Unclassified & Edge-Case email handling safety nets
don-petry 3fcd9cc
feat: add native Google Apps Script GitHub Sync module (UrlFetchApp -…
don-petry 1d65a71
chore(ci): apply prettier/eslint auto-fixes
github-actions[bot] c1ab1ba
fix(bot): address bot feedback [skip ci-relay]
donpetry-bot 899ad30
fix(bot): address bot feedback [skip ci-relay]
donpetry-bot d5f21ee
test: add performance and scalability benchmark test suite for long d…
don-petry ee1ae05
feat: enhance gitHubSync with targeted insertion and 409 SHA collisio…
don-petry f06010c
chore: dev-lead update (review-changes) [skip ci-relay]
donpetry-bot 64b46ec
feat: add Option A multi-account user email resolution and deployment…
don-petry b41cdd1
chore(ci): apply prettier/eslint auto-fixes
github-actions[bot] 48c8a3e
feat: add modelEndpoint to classifier config
donpetry-bot f1f8aa1
feat: add appsscript.json manifest for clasp deployment
don-petry 581cc10
chore: add .claspignore to exclude Node.js Jest test files from Apps …
don-petry 6f6185d
fix: export getAiClassifierConfig and standalone GAS logic
don-petry bcdde8f
fix: safe Gemini API HTTP status checking and response guards
don-petry ea2bf36
fix: update Gemini API endpoint to gemini-2.0-flash with automatic fa…
don-petry db9dad7
feat: add listAvailableGeminiModels helper and expanded model endpoints
don-petry 3c76f60
feat: set gemini-3.1-flash-lite as 2nd fallback endpoint
don-petry ea307b3
feat: add 404 auto-create note functionality with Progressive Disclos…
don-petry e75dc95
feat: reorder endpoints to highest-level model first and add HTTP 429…
don-petry 0cb6756
feat: set Gemini 3.5 Flash Lite as primary endpoint #1
don-petry 184fb59
feat: set Gemini 3.5 Flash Lite as #1 and Gemini 3.1 Flash Lite as #2
don-petry 8ecfaab
chore: remove deprecated 1.x and 2.x Gemini models, use 3.x models only
don-petry 466fad3
chore: dev-lead update (review-changes) [skip ci-relay]
donpetry-bot 683d5a9
feat: add Family/DJ-Rachel sub-label and strict prompt classification…
don-petry dff51b9
feat: exclude health newsletters and news bulletins from 04_Family_He…
don-petry b566b95
feat: add setupFiveMinuteTrigger and stopAllTriggers for automated ba…
don-petry 1656f46
feat: map student college/career planning meetings to Family/School
don-petry d813331
feat: map MyBroodMinder alerts to 07_Community_NonProfit with sub-lab…
don-petry 7037ee1
feat: map ParentSquare and MCAA school emails to Family/School-Toby
don-petry 4befa97
fix: add 2-second rate limit backoff delay on HTTP 429 and stabilize …
don-petry c0408e0
feat: parse and honor Retry-After header and retryDelay on HTTP 429
don-petry ba84dfb
chore: strictly enforce Gemini 3.x models ONLY
don-petry 28404ed
feat: add Gemma 4 31B (gemma-4-31b-it) as primary endpoint #1
don-petry 57590f5
fix: add extractJsonSubstring helper to parse Gemma 4 31B markdown fo…
don-petry a763bd6
feat: exclude tech webinars and commercial store marketing from core …
don-petry 1ab8558
feat: add automated action execution (trash, archive, mark_read) and …
don-petry File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
193 changes: 193 additions & 0 deletions
193
docs/proposals/proposal-001-ai-gemini-semantic-classifier.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,193 @@ | ||
| # 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`) 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. | ||
|
|
||
| --- | ||
|
|
||
| ## Implementation Prerequisites | ||
|
|
||
| ### 1. User Opt-In Configuration | ||
|
|
||
| The classifier requires explicit user opt-in before processing email content with an external AI API: | ||
|
|
||
| - A `GEMINI_CLASSIFIER_ENABLED` Script Property must be set to `true` to activate processing. | ||
| - Users must acknowledge that email metadata (sender, subject, body snippet) is transmitted to the Gemini API. | ||
|
|
||
| > **Not yet implemented:** The `GEMINI_CLASSIFIER_ENABLED` guard and user acknowledgment step are not present in the current version; `processEmailsWithAiClassifier()` runs unconditionally. | ||
|
|
||
| ### 2. Data Minimization & Redaction Boundary | ||
|
|
||
| Before sending email content to the Gemini API, the implementation applies: | ||
|
|
||
| - **Sender**: Full sender address (required for filter generation accuracy). | ||
| - **Subject**: Full subject line. | ||
| - **Body snippet**: Maximum 1,200 characters of plain text; HTML tags and quoted-reply blocks are stripped via `getCleanBody()` before transmission. _(Not yet implemented: the current version uses `getPlainBody().substring(0, 1200)` without quoted-reply stripping.)_ | ||
| - Sensitive patterns (e.g., SSN-like strings, credit card numbers) should be redacted from the body snippet before it is sent. _(Not yet implemented.)_ | ||
|
|
||
| ### 3. Gemini Provider Data-Retention | ||
|
|
||
| This implementation targets the Google Gemini API under a Google Workspace account. Administrators must verify the Gemini API data retention and logging settings for their organization before enabling this classifier on accounts containing sensitive or regulated data. | ||
|
don-petry marked this conversation as resolved.
|
||
|
|
||
| ### 4. API Key Storage (User-Only Script Properties) | ||
|
|
||
| The Gemini API key **must** be stored in Google Apps Script **Script Properties** (per-script, not Document or User Properties): | ||
|
|
||
| ```javascript | ||
| PropertiesService.getScriptProperties().setProperty( | ||
| 'GEMINI_API_KEY', | ||
| '<your-key>' | ||
| ) | ||
| ``` | ||
|
|
||
| Never hard-code the API key in `config.gs` or any committed file. | ||
|
|
||
| ### 5. Required OAuth Scopes | ||
|
|
||
| Declare the following scopes in `appsscript.json`: | ||
|
|
||
| ```json | ||
| { | ||
| "oauthScopes": [ | ||
| "https://www.googleapis.com/auth/gmail.labels", | ||
| "https://www.googleapis.com/auth/gmail.modify", | ||
| "https://www.googleapis.com/auth/gmail.settings.basic", | ||
| "https://www.googleapis.com/auth/script.external_request" | ||
| ] | ||
| } | ||
| ``` | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| The `Gmail.Users.Settings.Filters.create` call additionally requires the **Advanced Gmail Service** to be enabled in the Apps Script project under **Services**. | ||
|
|
||
| --- | ||
|
|
||
| ## Handling Unclassified & Edge-Case Mail | ||
|
|
||
| - **Transactional & 2FA Passcodes**: Verification codes, password resets, and 2FA emails are skipped by Drive ingestion to prevent clogging Drive with 1-line passcodes. | ||
| - **Inbox Safety Net**: Unclassified emails are **NEVER deleted or hidden**. They remain safely in the user's INBOX for standard reading and review. | ||
| - **Optional `Unclassified` Badge**: Ambiguous emails can be tagged with an optional `Unclassified` label for easy visual filtering. | ||
|
|
||
| --- | ||
|
|
||
| ## 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. | ||
| - **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, secondary properties, rental properties, property archives | | ||
| | **`02_Finance_Legal`** | Financial & Legal Protections | Taxes, banking, insurance policies, court records, custody, estate planning | | ||
| | **`03_Vehicles`** | Transportation & Assets | Vehicle searches, vehicle titles, maintenance logs | | ||
| | **`04_Family_Health`** | Family Welfare & Health | Children, school records, medical appointments, therapy sessions, educational programs | | ||
| | **`05_Tech_Infrastructure`** | IT & Home Automation | Home automation, NAS backups, scripting tools, network gear, software licenses | | ||
| | **`06_Work_Career`** | Professional Life & Income | Employment records, resumes, interview materials, expense reports, admin notes | | ||
| | **`07_Community_NonProfit`** | Civic Duty & Service | Charity organizations, community board roles, faith outreach | | ||
|
|
||
| --- | ||
|
|
||
| ## Classifier Response Contract | ||
|
|
||
| The Gemini classifier prompt requests a response conforming to the following JSON schema: | ||
|
|
||
| ```json | ||
| { | ||
| "canonical_label": "<one of the configured canonicalDomains strings>", | ||
| "confidence": 0.98, | ||
| "reasoning": "<one-sentence explanation>" | ||
| } | ||
| ``` | ||
|
|
||
| **Field definitions:** | ||
|
|
||
| - `canonical_label` (string, required): Must be one of the exact strings from `config.canonicalDomains`. The implementation validates this value and rejects responses that do not match a configured label. | ||
| - `confidence` (number, required): A float in the range `[0.0, 1.0]` representing the classifier's certainty. Values outside this range are treated as invalid. | ||
| - `reasoning` (string, required): A brief one-sentence explanation of the classification decision. | ||
|
|
||
| **Validation and fallback behavior:** | ||
|
|
||
| - If the Gemini API returns an HTTP error, malformed JSON, or a refused response, `classifyEmailWithGemini` returns `null` and the thread is skipped without modification (no label applied, no filter created). | ||
| - If the parsed response is missing required fields, or `canonical_label` does not exactly match one of the configured domains, the response is rejected and treated as `null`. | ||
| - An abstain/unknown outcome is implicit: any non-conforming response leaves the thread unlabeled and unprocessed, making it eligible for the next run. | ||
|
don-petry marked this conversation as resolved.
|
||
|
|
||
| --- | ||
|
|
||
| ## Self-Learning Gmail Filter Generation | ||
|
|
||
| When Gemini classifies a new sender with high confidence (>= 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. | ||
|
|
||
| **Confidence calibration and staged approval:** | ||
|
|
||
| - Only classifications with `confidence >= autoFilterConfidenceThreshold` (default: `0.95`) trigger filter creation. | ||
| - Senders categorized under sensitive domains (e.g., `02_Finance_Legal`, `04_Family_Health`) should be reviewed manually before trusting auto-generated filters in production. | ||
|
|
||
| **Duplicate filter detection:** | ||
|
|
||
| - Before creating a new filter, the implementation should call `Gmail.Users.Settings.Filters.list` to check for an existing filter matching the same sender address and skip creation if one already exists. | ||
|
|
||
| **Label existence and resolution:** | ||
|
|
||
| - The `ensureGmailLabel()` helper creates the target label if it does not exist before the filter is created. | ||
| - If label creation fails, filter creation is skipped for that sender. | ||
|
|
||
| **Correction and rollback semantics:** | ||
|
|
||
| - If a manual label correction is detected (user re-labels a thread after auto-classification), the script should update or delete the corresponding filter rule on the next run. | ||
| - A `rollbackGmailFilter()` operation can retrieve and delete previously created filters via `Gmail.Users.Settings.Filters.list` + `Gmail.Users.Settings.Filters.delete`. | ||
|
|
||
| --- | ||
|
|
||
| ## 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. | ||
| 2. **Self-Learning Gmail Filter Generation**: | ||
| - When Gemini classifies a new sender with high confidence (>= 95%), the script automatically creates a permanent static Gmail filter rule. | ||
| 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`: | ||
|
|
||
| ```text | ||
| src/gmail-ai-classifier/ | ||
| ├── README.md | ||
| ├── code.gs | ||
| ├── config.gs | ||
| ├── src/ | ||
| │ └── index.js | ||
| └── tests/ | ||
| ├── code.test.js | ||
| └── integration.test.js | ||
|
don-petry marked this conversation as 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` | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.