Skip to main content
ΑPROJECT 01 // CASE STUDY DOSSIER

Sift — Personal Email Assistant

Rules first, a small LLM only when needed, priority mail on my phone.

PROJECT METADATA DOSSIERINDEX // 01
ROLESolo Full-Stack Developer
YEAR2026
DURATION3 days
STACKPython, FastAPI, PostgreSQL, Kotlin
STATUSLIVE
AUTHORJeel Nada (solarquack)
ΑDOSSIER // 01
PAUSED
PLAY VIDEO DOSSIER
[SPACE] OR CLICK TO INITIALIZE
00:00//00:00
00 // SUMMARY STATEMENT

A self-hosted email assistant that watches all my Gmail accounts, runs my own rules first, calls a small LLM with my own key only for what the rules can't settle, and pushes priority mail to an Android app.

01 // PROBLEM

I have more than one Gmail account and placement season is running. The mail that matters (an online test that closes tonight, a fraud alert) arrives next to bank debit notices, newsletters and college circulars. Gmail's own notifications ring for all of it or none of it.

I wanted one thing that reads every account, stays quiet about the noise, and makes my phone ring for the few mails that need me now. I also didn't want to pay an AI to read a bank debit, so the AI had to be the last step, not the first.

02 // APPROACH

Every new mail goes through the same order, and the first step that decides wins:

  1. 01.
    My rules. They match the sender's authenticated domain (DKIM/SPF/DMARC from Gmail's own headers), so a spoofed "bank" mail never matches a bank rule.
  2. 02.
    Free signals. Gmail's Promotions and Social tabs, and subject templates I silenced before.
  3. 03.
    A small LLM, only if I added my own key. It returns the category, company, deadlines and links in one call, under a daily token cap.
  4. 04.
    Otherwise normal priority. Anything that fails is delivered, never hidden.
    Detection is a setting: poll Gmail every few seconds for free, or use Gmail watch + Pub/Sub. If push breaks, the scheduler notices missing mail and falls back to polling on its own.
03 // BUILD

Sift is a FastAPI backend with a worker and a scheduler, a Kotlin + Compose Android app, and a React web app served by the same API. It runs on my laptop and is reachable at sift.solarquack.in through a Cloudflare Tunnel.

  • ―
    Priority notifications: urgent, normal and digest each get their own Android channel. Urgent pushes are re-sent every 5 minutes until the phone confirms it got them.
  • ―
    Company timelines: placement mail (announcement, registration, test, interview, result) lands on one timeline per company across all accounts. When Sift can't tell which company a mail is about, the notification asks with one-tap buttons and remembers the answer.
  • ―
    To-dos and reminders: mail that needs action becomes a to-do, with reminders 24 h and 2 h before the deadline.
  • ―
    Rules editor: a live "N of your last 500 mails would match" preview, rules written in plain English (I review the draft before it saves), and a first scan that reads 30 days of headers, with no bodies and no AI, so I can silence senders in bulk.
  • ―
    Reader, search and compose: conversations are fetched live from Gmail and never stored, remote images stay blocked until I load them, search takes Gmail syntax plus Sift's own filters, and replies are real Gmail drafts with undo send and send later. Before anything reaches the LLM, phone numbers, roll numbers, Aadhaar/PAN and long numbers are redacted.
SYSTEM ARCHITECTURE TOPOLOGY
clientGmail (poll or Pub/Sub)serviceScheduler & Job QueueworkerRules → Heuristics → LLMstoragePostgreSQLclientFCM → Android AppWhat changed?check_account jobMail, timelines, to-dosUrgent / normal / digest
CLICK "EXPAND & INTERACT" FOR FULLSCREEN CANVAS
04 // LEARNED

The hardest bugs only showed up on real mail:

  • ―
    The first real mail deadlocked the worker. I locked the account row FOR UPDATE, and inserting the message on another connection needed a key-share lock on that same row. Switching to FOR NO KEY UPDATE with a lock timeout fixed it, and a regression test now goes through the real path.
  • ―
    My college placement cell showed up as "unverified" because its mail comes through a Google Groups list. The pes.edu results only exist in Gmail's ARC summary. Sift now trusts ARC only when Gmail reports arc=pass and Google sealed it, so forged ARC sets still fail.
  • ―
    The model read "tonight" as a date in 2023 because I never told it today's date. It also pushed a real person's short mail down to digest. Now the received time goes into the prompt, and mail without bulk signals never drops below normal. The push to my phone takes 0.1–1.4 s. The whole trip from Gmail to phone is about 23 s, and most of that is Gmail waiting around 17 s before it reports a new message. I can't fix that part, so I measured it instead of guessing.
EMPIRICAL METRICS & BENCHMARKS
PUSH TO PHONE< 1.5 s
AI CALLS ON BANK MAIL0
BACKEND TESTS190+
TOOLING JUSTIFICATION
FastAPI & PostgreSQLOne Postgres does the data and the job queue (SKIP LOCKED), so the laptop runs Sift with no Redis and no Docker.
Kotlin & Jetpack ComposeA native app gets FCM data messages, its own notification channel per priority, and a battery-optimization exemption so urgent mail actually rings.
NEXT DOSSIER // 02✦Β

Distributed Message Broker

Pure Java distributed commit log broker with binary wire framing.

↗