This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady.
What I Built (And Why I Needed It)
I have a confession: I genuinely, passionately hate reading emails.
It’s not just a minor annoyance—it's an executive function black hole. Every morning greets you with an overwhelming wall of administrative noise: marketing blasts disguised as product updates, vendor follow-ups, circular discussion threads, and calendar invites.
Because of this email avoidance, I’ve paid a real price. I've missed deadlines, forgotten critical follow-ups, and caused unnecessary chaos in my life. The wake-up call came when I nearly missed the mandatory document verification deadline for my scholarship. The notification wasn’t flagged or marked urgent; it was quietly buried inside paragraph four of an innocuous administrative newsletter. Missing that single deadline would have cost me thousands of dollars and months of bureaucratic grief.
And even on the days I forced myself to open my inbox, the second hurdle was waiting: I despise drafting email replies. Staring at a blank compose box, agonizing over how to sound polite, professional, and crisp just to say "Yes, received, let's meet Thursday" drains more cognitive bandwidth than writing 200 lines of complex backend code.
In honor of International Sloth Day and the "Slow and Steady" challenge, I decided to channel my inner sloth productively: if I'm too lazy to wade through email clutter and draft polite replies, I should build an AI agent to do it for me—without compromising my privacy.
Enter Local Email Agent: a local-first, privacy-preserving inbox assistant and ReAct co-pilot powered entirely by Google's open-weight Gemma 2 9B running locally via Ollama.
How It Solves My Three Core Problems
-
"I hate reading emails" → Zero-Effort Triage: The agent connects to IMAP in the background, strips away HTML/tracking clutter, and runs every incoming message through
gemma2:9b. It categorizes emails (action_needed,review,informational,low_priority) and generates a punchy, one-sentence digest so I never have to read wall-of-text threads again. - "I almost missed my scholarship verification" → Automated Deadline & Action Extraction: The prompt pipeline explicitly commands Gemma to detect time-sensitive requirements, submission dates, and pending action items. Imminent deadlines are flagged in high-visibility alert badges on the dashboard so nothing falls through the cracks.
- "I hate drafting replies" → ReAct Co-Pilot with Human-in-the-Loop Send: An interactive drawer assistant inspects thread context, formulates an appropriate response in seconds, and queues it for approval. I don't type a single word—I just review the proposed draft, tweak if necessary, and click Approve & Send over authenticated SMTP.
Most importantly: zero email data ever leaves my machine.
flowchart LR
subgraph Local Workstation
IMAP[Mailbox via IMAP SSL] -->|Sync UIDs| Core[FastAPI Sync Engine]
Core -->|Sanitize & Store| DB[(SQLite WAL Vault)]
Core -->|Structured Prompts| Ollama[Ollama: Gemma 2 9B]
Ollama -->|Classification & Drafts| Core
UI[React 18 SPA] <-->|REST API| Core
User((User)) -->|Approve Draft| UI
Core -->|Authenticated SMTP| Outbound[Outbound Mail Server]
end
Demo
Here is a step-by-step walkthrough of how Local Email Agent operates, from initial ingestion to verified email dispatch:
Step 1: The Zero-Effort Triage Dashboard
When you open the application, incoming emails synced from IMAP are pre-classified into intuitive categories (Action Needed, Review, Informational, Low Priority). Extracted action items and imminent deadlines are prominently highlighted so you know exactly what requires your attention without opening dense threads.

Figure 1: The main inbox triage view showing unread messages ranked by urgency with extracted deadlines.
Step 2: The ReAct Co-Pilot Drawer
Selecting any email opens the interactive side drawer. The agent operates using a ReAct (Reasoning + Acting) loop: it can call local tools to read thread context, summarize conversation history, or draft a targeted response tailored to the sender's tone.

Figure 2: The ReAct co-pilot analyzing email context and composing a reply draft.
Step 3: Human-in-the-Loop Review & Approval
Autonomy without safety is a liability. When the agent finishes drafting, clicking Send Reply opens an explicit approval modal displaying the recipient, subject line, and generated message body. You can inspect the text, make direct line edits if necessary, and approve the dispatch.

Figure 3: Human-in-the-loop confirmation modal ensuring no email leaves without explicit verification.
Step 4: Authenticated SMTP Dispatch & Audit Logging
Once approved, the backend securely dispatches the message over authenticated SMTP (supporting both port 465 SSL and port 587 STARTTLS) and creates a persistent audit entry in the local sent_emails SQLite table.

Figure 4: Instant visual confirmation after successful outbound SMTP transmission.
Quickstart Guide: Running Locally
The entire application is containerized with Docker Compose using host networking to communicate directly with your local Ollama instance:
# 1. Clone the repository
git clone https://github.com/Ewan-Dkhar/email-agent.git
cd email-agent
# 2. Configure mailbox and SMTP credentials
cp config.example.yaml config.yaml
# Edit config.yaml with your IMAP and SMTP server settings
# 3. Pull Google's Gemma 2 9B model in Ollama
ollama pull gemma2:9b
# 4. Start the stack
docker compose up -d
Navigate to http://localhost:5173 to start using your local email triage assistant.
Code
Local Email Agent is fully open source under the MIT License:
- GitHub Repository: https://github.com/Ewan-Dkhar/email-agent
-
Automated Test Suite: 37 passing unit and integration tests covering IMAP ingestion, Ollama structured generation, SQLite migrations, prompt injection sanitation, and authenticated SMTP dispatch (
backend/.venv/bin/pytest backend/tests).
How I Built It
The stack is designed for speed, isolation, and predictability:
-
Inference Engine: Ollama serving Google's
gemma2:9b. We enforce structured outputs via Pydantic schemas and deterministic JSON prompting, tuning temperature to0.2for classification stability and0.6for natural conversational drafting. -
Defensive Prompt Sanitization: Incoming emails from untrusted third parties are potential prompt injection vectors. The backend strips zero-width unicode characters, neutralizes pseudo-system markers (
<system>,[INST]), and isolates email contents in delimited context blocks before presenting them to Gemma. -
Sync Engine: Python 3.12 with
imaplib, normalizing multipart MIME structures, HTML formatting, and tracking message UIDs to avoid duplicate re-processing. -
Storage: SQLite operating in Write-Ahead Logging (
PRAGMA journal_mode=WAL) mode with explicit connection pooling and foreign key constraints. -
Outbound SMTP Client: Python's
smtpliboffloaded asynchronously usingasyncio.to_threadwith automatic switching between SSL (port 465) and STARTTLS (port 587). - Frontend: React 18, Vite, TypeScript, and TailwindCSS, served behind an optimized Nginx reverse-proxy container.
Why Open Innovation Matters
Email is arguably the most sensitive personal data stream we possess. It contains scholarship notices, bank records, tax forms, employment contracts, security verification codes, and private conversations.
Routing your entire mailbox history through proprietary third-party cloud APIs poses significant privacy risks, vendor lock-in, and ongoing API subscription costs.
Open-weight models like Gemma 2 change the equation. At 9 billion parameters, gemma2:9b delivers the instruction-following fidelity and reasoning capacity required for nuance, deadline extraction, and reply drafting, all while running comfortably on consumer workstation hardware. Open innovation means:
- Absolute Privacy: Your emails never leave your machine or train third-party models.
- No Recurring Fees: Zero token billing or API rate limits.
- Total Transparency: The entire pipeline—from prompt construction to email dispatch—is auditable and modifiable in code.
The Lazy Payoff
What does channeling your inner sloth actually save?
- Zero Triage Paralysis: Instead of wading through 40+ unread threads every morning, Gemma categorizes, extracts action items, and flags upcoming deadlines into a clean digest.
- No More Missed Scholarship/Grant Deadlines: High-stakes dates buried in dense administrative text are extracted immediately and highlighted on your dashboard.
- Effortless Responses: For routine administrative requests ("Can you confirm receipt?", "When can we meet?"), the ReAct co-pilot pre-drafts the response. You review it, make any minor adjustments, and click "Approve & Send".
- Daily Time Savings: Reduces typical email overhead from 35–45 minutes of daily dread down to under 5 minutes of glancing, approving, and moving on with your day.
Prize Categories
I am submitting Local Email Agent for the following challenge category:
-
Featured Category: Best Use of Gemma ($200): Local inference powered by Google's open-weight
gemma2:9bmodel running on Ollama. Gemma acts as both the structured extraction engine for inbox triage and the reasoning backbone of the ReAct co-pilot.
AI Disclosure: Development was assisted by Claude/Antigravity for pair-programming, test authoring, and drafting this submission narrative. All code, architecture, and tests were verified locally.
Top comments (0)