2025 · Case study
Smart Inbox Agent
An email assistant with a human still holding the pen.
An AI-powered professional email assistant that classifies incoming mail, drafts replies, and schedules meetings against the visitor's own Google Calendar — with a persistent human-in-the-loop review pipeline built on LangGraph interrupts.
- Python
- LangGraph
- Streamlit
- Groq API
- Google Calendar
- OAuth 2.0
- AI Agents
- NLP
01
The problem
Email automation usually fails in one of two directions: it's too dumb to be useful, or it's autonomous enough to send something embarrassing on your behalf.
Meeting scheduling makes it worse — proposing times requires real calendar state, and getting that wrong wastes more time than doing it manually.
The design goal was an assistant that does all the tedious work up front but never takes an irreversible action without an explicit human yes.
02
Architecture
The agent's workspace is a compiled LangGraph state machine. A Classify node categorises the incoming subject and body into urgent, meeting_request, newsletter or other using a Groq-hosted LLM.
The Draft node branches on that classification. For a meeting_request it queries the signed-in user's Google Calendar FreeBusy data and proposes exactly three available one-hour slots; otherwise it drafts a contextually appropriate reply.
The Approval node pauses execution with LangGraph's interrupt() and waits for human review. Feedback routes into a Refine node that polishes the response or slot list, capped at three revisions so it can't loop forever.
Only after approval — or discard, or hitting the revision cap — does the Action node execute: mock-sending the email or booking the chosen slot directly on the user's calendar.
Authentication is genuinely multi-user. Any visitor signs in with their own Google account and meetings land on their calendar, not on a shared developer account.
- Incoming email (subject + body)
- urgent / meeting_request / newsletter / other
- Draft reply
- Propose 3 calendar slots (FreeBusy)
- Approval node — LangGraph interrupt()
- Refine (max 3 revisions)
- Send reply / book the slot
The approval node loops back into refine until the human approves, discards, or the revision cap is hit.
03
Tech stack & why
Agent runtime
LangGraph (compiled StateGraph)
interrupt() gives first-class human-in-the-loop pausing instead of ad-hoc flags.
LLM
Groq API
Low-latency inference keeps classification and drafting feeling interactive.
Interface
Streamlit
Fast path to a reviewable dashboard with a visible node execution log.
Calendar
Google Calendar FreeBusy + Events API
Slot proposals come from real availability, and booking writes back to the user's own calendar.
Auth
Google OAuth 2.0, confidential web client
Per-visitor authorisation with the client secret held server-side.
04
Challenges & solutions
- 1
OAuth vs Streamlit's rerun model
PKCE is deliberately disabled (autogenerate_code_verifier=False). Streamlit's stateless rerun execution would otherwise lose or overwrite the code_verifier in session state during the redirect round-trip to Google and back. As a confidential web client holding its secret server-side, the app doesn't need PKCE to be safe.
- 2
Preventing infinite refinement
Human feedback loops can cycle indefinitely. The revision counter caps refinement at three passes and then forces the graph forward to the action node.
- 3
Multi-user calendar isolation
The architecture stores per-visitor credentials in session state so bookings always target the signed-in user's calendar rather than a global developer account.
05
What I'd build next
- Persist credentials and thread state in a database so a session survives a browser close, not just a rerun.
- Move from mock-sending to a real Gmail send integration behind the same approval gate.
- Add learned tone profiles so drafts match how the user actually writes.
Next case study
Image Descriptor