Skip to content

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MailInsight — Gmail Inbox Analyzer with AI-Powered Priority Classification

MailInsight is a full-stack web application that connects to a user's Gmail account(s) via Google OAuth2, fetches unread emails, and uses the Gemini AI to intelligently classify them by category and priority. It surfaces what actually demands attention — interview invites, security alerts, payment notices — while pushing newsletters, promotions, and social noise to the bottom. The result is a searchable, filterable dashboard with charts showing your inbox at a glance.


Table of Contents


Overview

A user signs in with Google, links one or more Gmail accounts, and provides their own Gemini API key. When they trigger an analysis, the backend fetches only unprocessed emails (incremental sync by message ID), batches them into groups of 20, and sends each batch to Gemini with a structured prompt. Gemini responds with a category, priority tier, and short summary per email. The results are validated, persisted to PostgreSQL, and rendered on the frontend as interactive charts and a paginated, searchable table.

The project is a modular monolith — a single Spring Boot backend with clearly separated layers (controller → service → repository), rather than microservices. This is a deliberate choice given the project scope and the benefits of simplicity and maintainability.


Tech Stack

Backend

Layer Technology
Language / Runtime Java
Framework Spring Boot
Security Spring Security, Spring Security OAuth2 Client
Persistence Spring Data JPA (Hibernate), PostgreSQL, HikariCP
Gmail Integration Google API Client Library (google-api-client, google-api-services-gmail)
AI Integration Gemini API (gemini-2.0-flash) via RestTemplate, per-user dynamic key injection
JSON Handling Jackson ObjectMapper
Encryption AES-256-GCM (javax.crypto) for OAuth token and API key storage
Utilities Lombok, SLF4J + Logback
Build Tool Maven

Frontend

Layer Technology
Framework React 18, Vite
Routing React Router v6
Charts Recharts
HTTP Native fetch with credentialed (cookie-based) requests
Styling CSS Modules

Architecture

+----------------------------------------------------------------------+
|                          CLIENT (Browser)                            |
|                                                                      |
|   React 18 + Vite                                                    |
|   +------------+  +-------------+  +--------------+  +----------+   |
|   | LoginPage  |  | OverviewPage|  |  EmailsPage  |  | Settings |   |
|   +-----+------+  +------+------+  +------+-------+  +----+-----+   |
|         |                |                |               |          |
|         +----------------+----------------+---------------+          |
|                                    |                                 |
|                          fetch() + Session Cookie                    |
+----------------------------------------------------------------------+
                                     |
                             HTTP (localhost:8080)
                                     |
+----------------------------------------------------------------------+
|                     SPRING BOOT BACKEND (Java 17)                    |
|                                                                      |
|  +------------------------------------------------------------------+|
|  |                     Spring Security Layer                        ||
|  |   OAuth2 Client Filter  -->  CustomOAuth2SuccessHandler          ||
|  |   Session Cookie (JSESSIONID)  -->  CustomUserDetailsService     ||
|  +------------------------------------------------------------------+|
|                              |                                       |
|  +----------------------------v-------------------------------------+|
|  |                      Controller Layer                            ||
|  |   AuthController | EmailController | AccountController           ||
|  |   UserController                                                 ||
|  +----------------------------+-+-----------------------------------+|
|                               | |                                    |
|  +----------------------------v-v-----------------------------------+|
|  |                       Service Layer                              ||
|  |                                                                  ||
|  |  +--------------+  +-------------+  +------------------+        ||
|  |  | EmailService |  | GmailService|  |  GeminiService   |        ||
|  |  | (orchestrate)|->| (Gmail API) |  |  (AI Batching)   |        ||
|  |  +------+-------+  +------+------+  +--------+---------+        ||
|  |         |                 |                   |                  ||
|  |  +------v-------+  +------v------+  +---------v--------+        ||
|  |  | AiKeyService |  | UserService |  | EncryptionUtil   |        ||
|  |  | (Key Mgmt)   |  | (User Mgmt) |  | (AES-256-GCM)   |        ||
|  |  +--------------+  +-------------+  +------------------+        ||
|  +----------------------------+-+-----------------------------------+|
|                               | |                                    |
|  +----------------------------v-v-----------------------------------+|
|  |                     Repository Layer                             ||
|  |   UserRepo | EmailRepo | ConnectedAccountRepo | AiKeyRepo        ||
|  +----------------------------+-+-----------------------------------+|
|                               | |                                    |
+--------------------------------|-|------------------------------------+
                                 | |
          +----------------------+ +--------------------+
          |                                             |
   +------v------+    +----------------+    +-----------v--+
   | PostgreSQL  |    |  Gmail API     |    |  Gemini API  |
   |  Database   |    |  (Google)      |    |  (Google AI) |
   +-------------+    +----------------+    +--------------+

Project Flow

1. Authentication

User visits app
   |
   v
LoginPage -> "Sign in with Google" button
   |
   v
Spring Security redirects -> Google OAuth2 Authorization Server
   |  (scope: openid, email, profile, gmail.readonly)
   v
Google returns authorization code
   |
   v
CustomOAuth2AuthorizedClientService
   |  +-- Encrypts access_token + refresh_token (AES-256-GCM)
   |  +-- Stores encrypted tokens in connected_accounts table
   |
   v
OAuth2SuccessHandler
   |  +-- Creates or updates User record in users table
   |  +-- Issues session cookie (JSESSIONID)
   |
   v
Frontend redirected to /dashboard

2. Linking Additional Gmail Accounts

User -> Settings -> "Connect another account"
   |
   v
GET /api/accounts/connect -> returns Google OAuth URL
   |
   v
User completes OAuth in popup/redirect
   |
   v
New ConnectedAccount row created for same User
   |  (each linked account has its own encrypted tokens)
   v
Emails from all accounts shown with their source label

3. Setting Up Gemini API Key

User -> Settings -> "Add API Key"
   |
   v
POST /api/user/api-key  { apiKey: "AIza..." }
   |
   v
AiKeyService
   |  +-- EncryptionUtil.encrypt(key) -> AES-256-GCM ciphertext
   |
   v
Stored in user_ai_keys table (never stored in plaintext)
   |
   v
Frontend shows key status: "Configured"

4. Email Analysis Pipeline (Core Flow)

User -> Dashboard -> "Analyze Emails" button
   |
   v
POST /api/emails/analyze
   |
   v
EmailService.analyze()
   |
   +-- For each linked ConnectedAccount:
   |      |
   |      v
   |   GmailService.fetchUnprocessedEmails()
   |      |  +-- Decrypt access_token from DB
   |      |  +-- Call Gmail API: messages.list (fetch only new IDs)
   |      |  +-- Track processed IDs via UserSyncMetadata
   |      |  +-- Fetch message details for new IDs only
   |      |
   |      v
   |   Threshold check: unprocessed count >= 10?
   |      |  no  -> return 422 { current: N, required: 10 }
   |      |  yes -> continue
   |      |
   |      v
   |   Batch emails (20 per batch)
   |      |
   |      v
   |   GeminiService.classifyBatch(emails, userApiKey)
   |      |  +-- EncryptionUtil.decrypt(user stored API key)
   |      |  +-- Build structured JSON prompt with email list
   |      |  +-- POST to Gemini API (gemini-2.0-flash)
   |      |  +-- Parse JSON response
   |      |  +-- Validate: category in allowed_set, priority in [1..5]
   |      |
   |      v
   |   Persist results to emails table
   |      |  (sender, subject, summary, category, priority, accountId)
   |      |
   |      v
   |   Mark batch as processed in UserSyncMetadata
   |
   v
Return analysis summary { analyzed: N, categories: {...} }
   |
   v
Frontend refreshes charts + email table

5. Dashboard View

GET /api/emails/stats           -> Recharts donut/bar charts (by category & priority)
GET /api/emails                 -> Paginated email table (sortable, filterable)
GET /api/emails/category/{cat}  -> Drill-down view per category
GET /api/emails/new/count       -> Progress indicator ("8 of 10 collected")

Core Backend Subsystems

Incremental sync. UserSyncMetadata tracks the last-seen Gmail history ID and processed message IDs per connected account. On each analysis run, only genuinely new emails are fetched — the entire inbox is never re-scanned.

Batch AI calls. Emails are grouped into batches of 20 and sent to Gemini in a single structured request per batch. This keeps AI call count proportional to batch count, not inbox size, and avoids per-minute rate limit issues.

Minimum threshold guard. Analysis only proceeds when at least 10 unprocessed emails are available. Below that, the endpoint returns HTTP 422 with the current count so the frontend can show "X of 10 collected" — preventing empty or near-useless AI requests.

Response validation. Gemini output is never written to the database blindly. The service validates JSON structure and checks that each email's assigned category and priority are members of the fixed allowed sets. Malformed or partial responses are handled per-email without failing the entire batch.

Bring-your-own-key model. Each user provides their own Gemini API key. It is encrypted with AES-256-GCM using a server-side secret and decrypted in memory only for the duration of an API call. The plaintext key is never stored or logged.

Multi-account support. A single user can link multiple Gmail accounts (personal, work, college). Each account is a separate ConnectedAccount row with its own encrypted OAuth tokens. Emails are tagged with their source account so the dashboard can distinguish them.

Classification taxonomy. Emails are classified into a fixed, curated set of categories grouped by domain (career, interviews, security, banking, learning platforms, newsletters, promotions, campus/community) and one of five priority tiers — from time-sensitive/high-priority down to low-priority/promotional. The taxonomy is fixed server-side to ensure consistent classification quality.


Database Schema

+---------------------+       +---------------------------+
|       users         |       |    connected_accounts     |
|---------------------|       |---------------------------|
| id (PK)             |--+    | id (PK)                   |
| google_id           |  |    | user_id (FK -> users.id)  |
| email               |  +--->| gmail_address             |
| name                |       | access_token  (encrypted) |
| picture_url         |       | refresh_token (encrypted) |
| created_at          |       | token_expiry              |
+---------------------+       +---------------------------+
         |
         |   +-----------------------------+
         |   |           emails            |
         |   |-----------------------------|
         |   | id (PK)                     |
         |   | user_id (FK -> users.id)    |
         +-->| account_id (FK -> conn_acc) |
             | gmail_message_id            |
             | sender                      |
             | subject                     |
             | ai_summary                  |
             | category                    |
             | priority  (1-5)             |
             | received_at                 |
             | processed_at                |
             +-----------------------------+

+---------------------+       +-----------------------------+
|    user_ai_keys     |       |    user_sync_metadata       |
|---------------------|       |-----------------------------|
| id (PK)             |       | id (PK)                     |
| user_id (FK)        |       | account_id (FK -> conn_acc) |
| encrypted_api_key   |       | last_history_id             |
| created_at          |       | processed_message_ids       |
| updated_at          |       | updated_at                  |
+---------------------+       +-----------------------------+

Note: Only email metadata is persisted — sender, subject, AI-generated summary, category, priority, and timestamps. Raw email bodies and attachments are read from Gmail, used in the Gemini request, and immediately discarded. They are never stored.


API Endpoints

Method Path Description
GET /api/auth/me Returns the currently logged-in user or 401
GET /api/auth/logout Invalidates the session
GET /api/emails Paginated, filtered list of analyzed emails
GET /api/emails/new/count Count of unprocessed emails (threshold progress)
GET /api/emails/stats Aggregate stats by category and priority (for charts)
GET /api/emails/category/{category} Paginated emails for a specific category
POST /api/emails/analyze Triggers fetch + AI classification pipeline
GET /api/user/api-key/status Whether a Gemini API key is configured
POST /api/user/api-key Save or update the user's Gemini API key
DELETE /api/user/api-key Remove the stored Gemini API key
GET /api/accounts List all linked Gmail accounts
GET /api/accounts/connect Get the OAuth URL to link an additional Gmail account
DELETE /api/accounts/{id} Unlink a Gmail account

Security

  • Authentication: Google OAuth2 only — no passwords are stored or managed by the application.
  • Gmail access: Read-only scope (gmail.readonly). The app cannot send, delete, modify, or label emails.
  • Token encryption: OAuth access and refresh tokens are encrypted at rest (AES-256-GCM) before being stored in the database.
  • API key encryption: Gemini API keys are encrypted at rest and decrypted only in memory, only for the duration of an API call. The plaintext key is never logged or returned to the frontend.
  • Sessions: Cookie-based (JSESSIONID), HTTP-only, invalidated on logout.
  • CORS: Configurable via app.cors.allowed-origin; defaults to http://localhost:5173 in local development.
  • Error handling: A global @ControllerAdvice exception handler ensures internal stack traces, tokens, and API keys are never exposed in API responses.

Scope and Limitations

The following are intentionally out of scope to keep the project focused:

  • No sending, replying to, deleting, or modifying emails — strictly read-only.
  • No attachment parsing — only subject, sender, and body text/snippet are used.
  • No real-time push sync — analysis is on-demand, triggered manually by the user.
  • No team or multi-tenant workspaces — each Google account maps to one independent user.
  • No offline mode or native mobile app.
  • No support for non-Gmail providers (Outlook, Yahoo, etc.).

Running Locally

Prerequisites

  • Java 17+
  • Node.js 18+
  • PostgreSQL (with a database named mailanalyzer)
  • A Google Cloud project with:
    • OAuth2 credentials (Web Application type)
    • Gmail API enabled
    • Authorized redirect URI: http://localhost:8080/login/oauth2/code/google
  • A Gemini API key from Google AI Studio

Backend

cd backend
./mvnw spring-boot:run

Runs on http://localhost:8080.

Frontend

cd frontend
npm install
npm run dev

Runs on http://localhost:5173.


Environment Variables

Backend (backend/src/main/resources/application.properties)

Property Description
spring.datasource.url PostgreSQL JDBC URL (e.g. jdbc:postgresql://localhost:5432/mailanalyzer)
spring.datasource.username Database username
spring.datasource.password Database password
spring.security.oauth2.client.registration.google.client-id Google OAuth2 client ID
spring.security.oauth2.client.registration.google.client-secret Google OAuth2 client secret
app.encryption.secret Base64-encoded 32-byte secret for AES-256-GCM encryption
app.cors.allowed-origin Allowed CORS origin (default: http://localhost:5173)

Frontend (frontend/.env)

Variable Description
VITE_API_BASE_URL Backend base URL (default: http://localhost:8080)

About

Full-stack email intelligence dashboard built with Spring Boot 3, React, and Gemini AI. Features OAuth2 Gmail integration, rate-limit safe batching, and AES-256 encrypted API key storage.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages