A powerful Chrome extension that helps you take back control of your inbox through intelligent sender analysis and bulk email management.
Features • Tech Stack • How It Works • Installation • Architecture
Mailzap is a Chrome extension that transforms Gmail inbox management from a tedious manual task into an automated, intelligent process. Built with modern web technologies, it analyzes your entire email history, identifies the biggest contributors to inbox clutter, and provides powerful bulk operations to clean up thousands of emails in just a few clicks.
- Email Overload: Thousands of promotional emails cluttering your inbox
- ⏱Time-Consuming: Manually unsubscribing from multiple senders takes hours
- No Visibility: Hard to identify which senders are the biggest culprits
- Repetitive Work: Deleting emails from the same senders over and over
Mailzap provides a comprehensive dashboard that:
- Scans your entire Gmail inbox and identifies all unique senders
- Ranks senders by email frequency (who sends you the most emails)
- Enables bulk selection of multiple senders at once
- Offers one-click operations: Unsubscribe, Delete, Block, or Search
- Automates the unsubscribe process by parsing email headers and bodies
- Runs entirely client-side for maximum privacy
|
|
|
|
|
|
| Technology | Version | Purpose | Where Used |
|---|---|---|---|
| React | 19.0.0 | Component-based UI framework with hooks | All UI components (sidebar, popup) |
| TypeScript | 5.7.2 | Static typing for better code quality | Entire codebase for type safety |
| CSS3 | - | Styling with custom animations | Component-specific stylesheets |
Why React? Enables component reusability, state management through Context API, and efficient re-rendering with hooks.
Why TypeScript? Catches errors at compile-time, provides better IDE support, and makes refactoring safer.
| Technology | Version | Purpose | Where Used |
|---|---|---|---|
| Vite | 6.2.0 | Fast build tool with HMR | Development server and production builds |
| TypeScript Compiler | 5.7.2 | Transpiles TS to JS | Build process (tsc -b) |
| ESLint | 9.21.0 | Code linting and quality checks | Enforces coding standards across all .ts/.tsx files |
| Prettier | 3.5.3 | Code formatting | Formats all files consistently |
| Stylelint | 16.18.0 | CSS linting | Validates CSS files |
Why Vite? Lightning-fast hot module replacement (HMR), optimized production builds, and better developer experience compared to webpack.
Vite Configuration Highlights:
- Multi-page setup with separate entry points for sidebar and popup
- Public directory for static assets and manifest
- React plugin for JSX transformation
| API | Purpose | Where Used |
|---|---|---|
| chrome.identity | OAuth 2.0 authentication with Google | chromeAuth.ts - Getting/validating tokens |
| chrome.storage.local | Local data persistence | Storing sender data, fetch progress, user state |
| chrome.sidePanel | Side panel UI on Gmail pages | background.js - Enabling panel on Gmail |
| chrome.tabs | Tab management and messaging | Content script communication, opening links |
| chrome.runtime | Background script lifecycle | Message passing between components |
| chrome.action | Extension icon behavior | Popup toggle based on current site |
Manifest V3 Compliance:
- Service worker instead of background page
host_permissionsreplaced with minimalpermissions- CSP-compliant script loading
| API Endpoint | Purpose | Where Used |
|---|---|---|
/gmail/v1/users/me/messages |
Fetch message IDs and metadata | fetchMessageIds.ts, fetchSenders.ts |
/gmail/v1/users/me/messages/{id} |
Get specific message details | fetchSenders.ts (headers), unsubscribeSenders.ts (body) |
/gmail/v1/users/me/messages/{id}/trash |
Move messages to trash | trashSenders.ts |
/gmail/v1/users/me/messages/send |
Send unsubscribe emails | unsubscribeSenders.ts (mailto unsubscribe) |
/gmail/v1/users/me/settings/filters |
Create email filters | realActions.ts (block sender) |
/oauth2/v2/userinfo |
Verify OAuth tokens | chromeAuth.ts (token validation) |
Rate Limiting Handling:
- Automatic retry with exponential backoff on 429/403 errors
- Batch processing (40 messages at a time)
- Sleep functions to respect API quotas
| Pattern | Technology | Where Used |
|---|---|---|
| React Context API | Built-in React | Global state for actions, login status, modal state, selected senders |
| React Hooks | useState, useEffect, useContext | Component state and lifecycle management |
| Chrome Storage | chrome.storage.local API | Persistent storage for sender data across sessions |
Context Providers:
ActionsContext- Provides Gmail API operations (mock/real modes)LoggedInContext- Tracks OAuth authentication stateModalContext- Manages modal popups for confirmationsSelectedSendersContext- Tracks which senders are selectedSendersContext- Manages sender list and reload logic
| Tool | Version | Purpose | Configuration |
|---|---|---|---|
| Jest | 29.7.0 | Unit testing framework | jest.config.json with ts-jest preset |
| Playwright | 1.52.0 | End-to-end testing | E2E tests for user workflows |
| ESLint | 9.21.0 | JavaScript/TypeScript linting | eslint.config.js with React rules |
| Prettier | 3.5.3 | Code formatter | Consistent formatting across codebase |
| Stylelint | 16.18.0 | CSS linting | Validates CSS syntax and conventions |
| JSCPD | 4.0.5 | Copy-paste detection | Identifies code duplication |
Testing Strategy:
- Unit tests for utility functions (Gmail API wrappers)
- Mock environment for development (
VITE_USE_MOCK=true) - TypeScript ensures type correctness at compile-time
| Library | Purpose | Where Used |
|---|---|---|
| FontAwesome | Icon library | UI components (buttons, headers) |
| react-loading-skeleton | Skeleton loading screens | senderLineSkeleton.tsx |
| cross-env | Cross-platform env variables | npm scripts for mock mode |
graph TD
A[User Installs Extension] --> B[Opens Gmail]
B --> D[User Clicks Extension Icon]
D --> E[Side Panel Opens]
E --> F[OAuth Login Required?]
F -->|Yes| G[Google Sign-In]
F -->|No| H[Check Storage for Senders]
G --> H
H -->|Data Exists| I[Display Sender List]
H -->|No Data| J[Fetch All Emails]
J --> K[Progress Bar: 0-100%]
K --> L[Parse Sender Info]
L --> M[Store in Chrome Storage]
M --> I
I --> N[User Selects Senders]
N --> O{User Action?}
O -->|Unsubscribe| P[Parse Unsubscribe Methods]
O -->|Delete| Q[Bulk Trash Emails]
O -->|Search| R[Open Gmail Search]
P --> S[Auto Unsubscribe]
S --> T{Has Mailto?}
T -->|Yes| U[Send Unsubscribe Email]
T -->|No| V{Has Click Link?}
V -->|Yes| W[Open Link in New Tab]
V -->|No| X[Offer to Block]
U --> Y[Delete Emails?]
W --> Y
X --> Y
Y -->|Yes| Q
Q --> Z[Refresh Sender List]
Z --> I
1️⃣ Authentication Flow
What Happens:
- User clicks extension icon on Gmail
- Side panel opens (popup elsewhere)
- Extension checks for valid OAuth token via
chrome.identity.getAuthToken() - If no token, shows login screen
- User clicks "Sign in with Google"
- Google OAuth consent screen appears
- Token is cached by Chrome
Code: chromeAuth.ts - signInWithGoogle(), getValidToken()
Security:
- OAuth scopes:
gmail.modify,gmail.settings.basic,userinfo.email - Token verified against current Gmail account
- Mismatched accounts trigger re-authentication
2️⃣ Email Scanning & Analysis
What Happens:
- Check Chrome storage for existing sender data
- If not found, call
fetchAllSenders(accountEmail) - Fetch all message IDs from Gmail API (500 per page)
- Paginate through all pages until complete
- Batch process 40 messages at a time
- For each message, fetch
Fromheader via/messages/{id}?format=metadata - Parse sender email and name from header
- Aggregate counts by email address
- Store in Chrome storage as sorted array
Code: fetchSenders.ts, fetchMessageIds.ts
Performance:
- Progress bar updates every 40 messages
- Rate limiting with 1-second retry on 429 errors
- Typical inbox (10K emails) scans in ~2-3 minutes
Storage Format:
{
"user@gmail.com": {
"senders": [
["sender@example.com", "Sender Name", 150, "msgId123"],
// ... more senders
]
}
}3️⃣ Sender Selection & Display
What Happens:
- Retrieve sorted sender list from storage
- Filter out Gmail addresses (
@gmail.com) - Render each sender as a
SenderLinecomponent - Show checkbox, name, email, and count
- User can select/deselect with checkbox
- Selected state stored in
SelectedSendersContext
Code: sendersContainer.tsx, senderLine.tsx
UI Features:
- Skeleton loading during initial fetch
- Reload button to refresh data
- Email address clickable to search Gmail
- Visual selection highlight
4️⃣ Unsubscribe Operation
What Happens:
- User selects senders and clicks "Unsubscribe"
- Confirmation modal shows with options:
- Delete emails after unsubscribe (default: ON)
- Also block senders (default: OFF)
- User confirms, triggers
unsubscribeFlow - For each sender:
a. Get latest message ID from storage
b. Fetch message headers via Gmail API
c. Look for
List-Unsubscribeheader d. Parse mailto/posturl/clickurl e. If mailto exists: Send unsubscribe email via Gmail API f. If only click link: Open link, wait for user confirmation g. If neither: Offer to block sender instead - Optionally delete all emails from sender
- Optionally create Gmail filter to auto-trash future emails
- Update storage and refresh UI
Code: unsubscribeSenders.ts, unsubscribeFlow.tsx
Unsubscribe Methods:
- mailto:
<mailto:unsubscribe@example.com>→ Automated email - https:
<https://example.com/unsubscribe>→ Manual click - None: Fallback to blocking via Gmail filter
5️⃣ Delete Operation
What Happens:
- User selects senders and clicks "Delete"
- Confirmation modal shows count of emails
- User confirms, triggers
deleteSenders() - For each sender:
a. Fetch all message IDs via Gmail API (max 500)
b. Call
/messages/{id}/trashfor each message - Remove senders from Chrome storage
- Refresh sender list (they disappear)
Code: trashSenders.ts, realActions.ts
Notes:
- Emails moved to trash (recoverable for 30 days)
- Not permanent delete for safety
- Fast operation (~1-2 seconds per sender)
6️⃣ Block Sender
What Happens:
- Creates Gmail filter via
/settings/filtersendpoint - Filter criteria:
from: sender@example.com - Filter action: Add label
TRASH - All future emails from sender auto-trashed
Code: realActions.ts - blockSender()
Use Case:
- For senders with no unsubscribe option
- Prevents future emails from cluttering inbox
- User can manually remove filter later in Gmail settings
Mailzap/
├── 📁 public/ # Static assets & extension files
│ ├── manifest.json # Chrome extension configuration (Manifest V3)
│ ├── background.js # Service worker (side panel logic)
│ ├── content.js # Content script (Gmail page interaction)
│ ├── 📁 images/ # Extension icons (16x16 to 128x128)
│ └── 📁 assets/ # Built static assets (Vite output)
│
├── 📁 src/ # Source code
│ ├── index.html # Root HTML (not used in extension)
│ ├── index.css # Global styles
│ ├── vite-env.d.ts # Vite type definitions
│ │
│ ├── 📁 _shared/ # Shared utilities & types
│ │ ├── 📁 providers/
│ │ │ ├── actionsContext.tsx # Global actions provider (mock/real modes)
│ │ │ └── loggedInContext.tsx # Authentication state provider
│ │ ├── 📁 types/
│ │ │ └── types.ts # TypeScript interfaces (Sender, UnsubscribeData)
│ │ └── 📁 utils/
│ │ ├── chromeAuth.ts # OAuth 2.0 authentication logic
│ │ ├── fetchMessageIds.ts # Paginated message ID fetching
│ │ ├── fetchSenders.ts # Sender analysis & aggregation
│ │ ├── trashSenders.ts # Bulk email deletion
│ │ ├── unsubscribeSenders.ts # Unsubscribe automation
│ │ ├── utils.ts # Helper functions (parsing, sleep, etc.)
│ │ └── 📁 actions/
│ │ ├── actionsInterface.ts # Actions interface definition
│ │ ├── realActions.ts # Production Gmail API actions
│ │ └── mockActions.ts # Development mock actions
│ │
│ ├── 📁 popup/ # Extension popup (non-Gmail pages)
│ │ ├── index.html # Popup entry point
│ │ ├── main.tsx # React entry (renders PopupApp)
│ │ ├── Popup.tsx # Simple info popup with links
│ │ └── Popup.css # Popup styles
│ │
│ └── 📁 sidebar/ # Side panel UI (Gmail pages)
│ ├── index.html # Sidebar entry point
│ ├── main.tsx # React entry (renders App with providers)
│ ├── App.tsx # Main app component (login check, layout)
│ ├── App.css # Global sidebar styles
│ ├── 📁 components/ # Reusable UI components
│ │ ├── header.tsx # Top header with logo
│ │ ├── loadingBar.tsx # Progress bar during email fetch
│ │ ├── senderLine.tsx # Single sender row (checkbox, name, email, count)
│ │ ├── senderLineSkeleton.tsx # Loading skeleton for sender rows
│ │ ├── sendersContainer.tsx # Main container for sender list
│ │ ├── actionButton.tsx # Unsubscribe/Delete buttons
│ │ ├── reloadButton.tsx # Reload sender data button
│ │ ├── modalPopup.tsx # Modal for confirmations/errors/success
│ │ ├── toggleSwitch.tsx # Toggle switch component
│ │ ├── toggleOption.tsx # Toggle option with label
│ │ └── 📁 login-page/
│ │ └── loginPage.tsx # Login screen UI
│ ├── 📁 providers/ # Sidebar-specific context providers
│ │ ├── allGlobalProviders.tsx # Wrapper for all providers
│ │ ├── modalContext.tsx # Modal state management
│ │ ├── selectedSendersContext.tsx # Selected senders state
│ │ └── sendersContext.tsx # Sender list state
│ └── 📁 utils/
│ └── unsubscribeFlow.tsx # Unsubscribe workflow orchestration
│
├── 📁 extras/ # Marketing assets (logo, promo images)
│
├── 📁 dist/ # Production build output (Vite)
│
├── 📄 vite.config.ts # Vite build configuration
├── 📄 tsconfig.json # TypeScript configuration
├── 📄 eslint.config.js # ESLint rules
├── 📄 jest.config.json # Jest test configuration
├── 📄 package.json # Dependencies & scripts
└── 📄 README.md # This file!
App (sidebar/App.tsx)
├── If not logged in:
│ └── LoginPage
│ └── GoogleAuthButton
├── If logged in:
└── SelectedSendersProvider
└── SendersProvider
└── ModalProvider
├── DeclutterHeader
├── ButtonBar
│ ├── ActionButton (Unsubscribe)
│ ├── ActionButton (Delete)
│ └── ReloadButton
├── SendersContainer
│ ├── LoadingBar (if fetching)
│ ├── SenderLineSkeleton (if loading)
│ └── SenderLine[] (list of senders)
│ ├── Checkbox
│ ├── Sender Details (name, email)
│ └── Email Count
└── ModalPopup
├── UnsubscribeConfirm
├── UnsubscribePending
├── UnsubscribeContinue
├── UnsubscribeError
├── UnsubscribeSuccess
├── DeleteConfirm
├── DeletePending
├── DeleteSuccess
└── NoSender
┌─────────────────┐
│ Gmail API │ ◄──── OAuth Token ────► chrome.identity
└────────┬────────┘
│
│ 1. Fetch message IDs
│ 2. Fetch message headers
│ 3. Trash messages
│ 4. Send emails
│ 5. Create filters
│
▼
┌──────────────────────────────────────────┐
│ Shared Utilities (_shared/utils) │
│ • fetchMessageIds.ts │
│ • fetchSenders.ts │
│ • trashSenders.ts │
│ • unsubscribeSenders.ts │
│ • chromeAuth.ts │
└─────────┬────────────────────────────────┘
│
│ Exposed via Actions Interface
│
▼
┌──────────────────────────────────────────┐
│ ActionsContext Provider │
│ • realActions (production) │
│ • mockActions (development) │
└─────────┬────────────────────────────────┘
│
│ Consumed by UI Components
│
▼
┌──────────────────────────────────────────┐
│ Sidebar Components │
│ • App.tsx │
│ • sendersContainer.tsx │
│ • senderLine.tsx │
│ • actionButton.tsx │
│ • modalPopup.tsx │
└──────────────────────────────────────────┘
│
│ State Management
│
▼
┌──────────────────────────────────────────┐
│ React Context Providers │
│ • SelectedSendersContext │
│ • SendersContext │
│ • ModalContext │
│ • LoggedInContext │
└─────────┬────────────────────────────────┘
│
│ Persistence Layer
│
▼
┌──────────────────────────────────────────┐
│ chrome.storage.local │
│ • Sender data (by email account) │
│ • Fetch progress (0-100%) │
│ • No sensitive data stored │
└──────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Chrome Browser │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Gmail Tab (mail.google.com) │ │
│ │ │ │
│ │ ┌──────────────────┐ ┌──────────────────────┐ │ │
│ │ │ Gmail DOM │◄────►│ content.js │ │ │
│ │ │ • Search bar │ │ • searchEmailSenders│ │ │
│ │ │ • Email list │ │ • getEmailAccount │ │ │
│ │ │ • Page title │ └──────────┬───────────┘ │ │
│ │ │ │ │
│ └────────────────────────────────────────┼──────────────┘ │
│ │ │
│ ┌────────────────────────────────────────┼──────────────┐ │
│ │ Chrome Extension Sidebar │ │ │
│ │ │ │ │
│ │ ┌─────────────────────────────────────▼────────────┐ │ │
│ │ │ sidebar/index.html (React App) │ │ │
│ │ │ • Sender list UI │ │ │
│ │ │ • Action buttons │ │ │
│ │ │ • Modal popups │ │ │
│ │ └─────────────────────────────┬────────────────────┘ │ │
│ │ │ │ │
│ └─────────────────────────────────┼─────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────▼──────────────────────┐ │
│ │ Background Service Worker │ │
│ │ background.js │ │
│ │ • Side panel enable/disable logic │ │
│ │ • Popup toggle based on current URL │ │
│ │ • Message routing between components │ │
│ └─────────────────────────────┬──────────────────────────┘ │
│ │ │
└────────────────────────────────┼────────────────────────────┘
│
┌────────────┴────────────┐
│ │
┌──────────▼──────────┐ ┌─────────▼─────────┐
│ chrome.storage │ │ chrome.identity │
│ • Sender data │ │ • OAuth tokens │
│ • Fetch progress │ │ • Token cache │
└─────────────────────┘ └───────────────────┘
|
|
🔴 Challenge 1: Gmail API Rate Limiting
Problem: Gmail API has quota limits (e.g., 250 quota units/second, 25,000/day per user). Scanning large inboxes (10K+ emails) would hit 429 errors.
Solution:
- Implemented automatic retry with
sleep(1000)on 429/403 errors - Batch processing (40 messages at a time) to reduce concurrent requests
- Progress tracking to show user real-time status
- Efficient metadata-only fetching (
format=metadata)
Code: fetchSenders.ts lines 79-82, fetchMessageIds.ts lines 51-55
🟡 Challenge 2: Unsubscribe Method Variability
Problem: Emails use different unsubscribe methods:
List-Unsubscribeheader with mailtoList-Unsubscribeheader with HTTP POSTList-Unsubscribe-Postheader- HTML body with
<a>tag - No unsubscribe option at all
Solution:
- Multi-layered parsing: headers first, then body fallback
- Automated flow for mailto (send email via Gmail API)
- Manual flow for click-only links (open tab, wait for user)
- Blocking as last resort (Gmail filter)
Code: unsubscribeSenders.ts - getUnsubscribeData(), unsubscribeFlow.tsx
🟢 Challenge 3: Managing Complex State
Problem: Multiple components need access to:
- Sender list (fetched from API)
- Selected senders (checkbox state)
- Modal state (confirmation dialogs)
- Login state (OAuth status)
Solution:
- React Context API with separate providers for each concern
- Custom hooks to access context (
useActions,useSenders) - Provider composition pattern in
allGlobalProviders.tsx
Code: _shared/providers/, sidebar/providers/
🔵 Challenge 4: Chrome Extension Manifest V3
Problem: Manifest V3 deprecated background pages in favor of service workers, breaking many V2 patterns.
Solution:
- Migrated to service worker model (
background.jsas module) - Used
chrome.storage.localinstead of in-memory variables - Switched to message passing for content script communication
- Ensured CSP compliance (no inline scripts)
Code: manifest.json, background.js
🟣 Challenge 5: TypeScript Testing with Dependency Injection
Problem: Need to unit test functions that call Chrome APIs, but Chrome APIs don't exist in Jest environment.
Solution:
- Dependency injection via optional
depsparameter exportForTestpattern to expose private functions- Mock implementations in
mockActions.ts - Environment variable to toggle mock mode (
VITE_USE_MOCK)
Code: chromeAuth.ts line 27-31, actionsContext.tsx line 7
- Node.js 18+ (for development)
- npm or yarn
- Google Chrome browser (version 109+)
- Gmail account
- Visit the Chrome Web Store page (link coming soon)
- Click "Add to Chrome"
- Click "Add extension" in the confirmation dialog
- Open Gmail
- Click the Mailzap icon in the toolbar
- Sign in and start decluttering!
git clone https://github.com/Mohit-Alive/Mailzap.git
cd Mailzapnpm installnpm run dev- Opens Vite dev server at
http://localhost:5173 - Uses mock actions (no real Gmail API calls)
- Hot module replacement enabled
npm run build- Compiles TypeScript to JavaScript
- Bundles React components with Vite
- Outputs to
dist/folder - Ready for Chrome extension loading
- Open Chrome and go to
chrome://extensions/ - Enable "Developer mode" (top-right toggle)
- Click "Load unpacked"
- Select the
dist/folder from this project - Extension appears in toolbar!
- Navigate to https://mail.google.com
- Click the Mailzap icon → Side panel opens
- Sign in with Google (OAuth prompt)
- Wait for email scanning to complete
- Select senders and test actions!
# Development with hot reload (mock mode)
npm run dev
# Production build
npm run build
# Run tests (Jest)
npm test
# Run linters
npm run lint:js # ESLint
npm run lint:css # Stylelint
npm run lint:prettier # Prettier
npm run lint # All linters + duplicate detection
# Format code
npm run lint:prettierCreate a .env file in the root directory:
# Enable mock mode (no real API calls)
VITE_USE_MOCK=trueIf you want to deploy your own version:
- Go to Google Cloud Console
- Create a new project
- Enable Gmail API
- Create OAuth 2.0 credentials (Chrome Extension type)
- Add authorized origins:
https://mail.google.com - Copy Client ID to
manifest.jsonunderoauth2.client_id
Required Gmail API Scopes:
https://www.googleapis.com/auth/gmail.modifyhttps://www.googleapis.com/auth/gmail.settings.basichttps://www.googleapis.com/auth/userinfo.email
Coming Soon! A comprehensive video walkthrough will be added here demonstrating:
- Installation process
- First-time setup and authentication
- Scanning emails and viewing sender analytics
- Bulk unsubscribe operation
- Bulk delete operation
- Block sender functionality
- All edge cases and error handling
Contributions are welcome! Here's how you can help:
- Fork the repository
- Create a feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
- Follow existing code style (run
npm run lintbefore committing) - Write tests for new features
- Update documentation as needed
- Keep commits atomic and well-described