Generated by reading the full backend (FastAPI) and frontend (Next.js) source trees. This document explains what the project actually does, how it is built, and how the pieces connect.
Mailgent (frontend branding) is a full‑stack, AI‑powered Gmail client. A user signs in with their real Google account (OAuth), and the app then acts as a smarter layer on top of their actual Gmail mailbox:
- Browse Inbox / Sent / Drafts like a normal webmail client (read, star, archive, trash, spam, delete, bulk actions).
- Compose, reply, reply‑all, and forward emails — either manually or AI‑generated.
- A conversational AI chat assistant ("AI Assistant" / chat page) that can read and act on the user's actual mailbox using tool-calling (search inbox, create/update/send drafts, reply, forward, trash/star/archive, etc.).
- Automatic replies: a background scheduler periodically scans new mail and, using an LLM, decides whether a message deserves an auto‑reply, then sends one in the user's voice, respecting the user's stored preferences.
- Document knowledge base (RAG): users upload PDFs; the app extracts text, chunks it, embeds it (HuggingFace embeddings), and stores vectors in Pinecone. The chat agent and the auto‑reply agent can retrieve from these documents to ground answers/replies.
- User preferences: free-form name/value rules (e.g. "always sign emails with...", "keep tone formal") that are injected as hard constraints into every AI agent's system prompt.
- Gmail search with local logging of search history.
- Dashboard stats (counts of AI actions, auto-replies sent, etc.) pulled from an audit table that logs every AI-performed email action.
- A /qalam/marks-change webhook endpoint used to email students automatically when an external system ("QALAM", a results portal) reports a grade change — a bespoke integration outside the normal user-driven flows, protected by a static API key header.
In short: it's a Gmail webmail clone + an agentic AI email assistant + a RAG knowledge base + an autonomous auto‑reply bot, all wired to a single Google account per user.
┌─────────────────────────┐ HTTPS / JSON, Bearer JWT ┌───────────────────────────┐
│ Next.js 16 frontend │ ───────────────────────────────────▶ │ FastAPI backend │
│ (React 19, Tailwind 4) │ ◀─────────────────────────────────── │ (Python, Uvicorn) │
│ Deployed: Vercel │ │ │
└─────────────────────────┘ └───────────┬────────────────┘
│
┌───────────────────────────────────────────────────────────────────────────┐
│ │
┌───────────▼───────────┐ ┌────────────▼─────────────┐ ┌─────────────▼─────────────┐
│ MySQL (via SQLAlchemy)│ │ Google Gmail API (OAuth) │ │ Pinecone vector DB │
│ users, gmail_accounts,│ │ read/send/modify mail on │ │ per-user namespace │
│ preferences, docs, │ │ behalf of the signed-in │ │ "user_id_{id}_documents" │
│ ai_email_actions, │ │ Google account │ │ │
│ search_queries │ └────────────────────────────┘ └─────────────────────────────┘
└────────────────────────┘
┌────────────────────────────┐ ┌──────────────────────────────┐
│ Azure-hosted OpenAI (LLM) │ │ HuggingFace Inference API │
│ model: gpt-4.1-mini │ │ sentence-transformers/ │
│ used by LangChain agents │ │ all-MiniLM-L6-v2 (embeddings)│
└────────────────────────────┘ └──────────────────────────────┘
The backend is a modular FastAPI app; each domain (auth, gmail, chatbot, embeddings,
preferences, search, stats, auto_reply) is its own package with a
*_controller.py (routes), *_service.py (business logic), and sometimes a DTO.py
(Pydantic request/response models).
- FastAPI (
app/main.py) + Uvicorn, CORS enabled for the deployed frontend (https://gmail-assistant-eight.vercel.app) and localhost. - SQLAlchemy (raw
text()SQL, not the ORM query layer) against MySQL (pymysqldriver). Connection settings come from.envviapydantic_settings. - Auth: Google OAuth "authorization code" flow, exchanged server-side for Google
access/refresh tokens; the backend then issues its own JWT (
PyJWT) that the frontend stores inlocalStorageand sends asAuthorization: Bearer <token>on every API call. All protected routes depend onget_current_user, which just decodes/verifies that JWT (no DB hit) and returns{user_id, name, email}. - Gmail integration:
google-api-python-client+google-auth. A per-requestGmailServicewrapper is built from the user's stored refresh token (fetched from MySQLgmail_accounts.refresh_token), which is exchanged for a short-lived access token on every call (no caching). - AI orchestration:
langchain,langchain_openai(ChatOpenAIagainst an Azure OpenAI-compatible endpoint, modelgpt-4.1-mini), and LangGraph (langgraph) for a small state-graph based router that dispatches a chat message to one or more specialized "agents" and merges their results. - RAG / embeddings:
pinecone(vector store),huggingface_hub(InferenceClientfor embeddings viasentence-transformers/all-MiniLM-L6-v2),langchain-pinecone,langchain_text_splitters(RecursiveCharacterTextSplitter),pypdf/PyPDF2for PDF text extraction. - Scheduling:
apscheduler(BackgroundScheduler) — wired up but currently commented out inmain.py's startup event, so auto‑reply currently only runs if triggered manually/externally rather than automatically every 10 minutes.
| Folder | Purpose |
|---|---|
auth/ |
Google OAuth login/callback, JWT issuing & verification (jwt/service.py) |
gmail/ |
All Gmail CRUD: inbox/sent/drafts, star/read/archive/trash/spam, send/reply/forward/draft, email parsing (Parser/) |
chatbot/ |
Chat endpoint + the agent system: agent/gmail_agent.py, agent/knowledge_agent.py, agent/RAG_agent.py, LangGraph router in agent/langgraph/ |
auto_reply/ |
Background job that scans new mail per user and auto-replies via LLM, respecting user preferences |
embeddings/ |
Upload PDFs, extract → chunk → embed → upsert to Pinecone; list/delete documents |
preferences/ |
CRUD for user-defined AI behavior preferences (name/value/enabled) |
search/ |
Server-side Gmail search (keyword scoring over live Gmail API results) + recent search history |
stats/ |
Dashboard counts sourced from the ai_email_actions audit table |
database/ |
SQLAlchemy engine/session setup, Pydantic settings, SQLAlchemy models (users, search_queries), the ai_action_service audit logger |
MySQL database gmail_assistant:
users— id, google_id, name, email, profile_picture.gmail_accounts— one row per connected Gmail account per user:refresh_token,status(ACTIVE/INACTIVE),auto_reply(bool toggle),last_history_id/last_auto_reply_check_at(Gmail History API cursor used to find "new since last check" messages for auto-reply).gmail_syncs— placeholder table for initial-sync bookkeeping (not actively used in the code read).search_queries— logs every Gmail search a user runs (query,is_ai_query,result_count) — powers "recent searches".chat_threads/chat_messages— schema exists for persisted chat history, but the current chatbot implementation keeps conversation state in-memory via LangGraph'sMemorySavercheckpointer keyed byf"{user_id}_{date.today()}", not in these tables.user_preferences—preference_name+preference_value(free text) +enabled, unique per(user_id, preference_name). Injected into every AI agent's system prompt as hard constraints.ai_email_actions— an audit/event log of every AI-driven mailbox action (draft created/updated, reply generated, email sent/trashed/deleted/starred/archived/ spam-marked/etc.), withstatus(pending/completed/failed), input/output text, and JSONmetadata. This table is what powers the dashboard stats.documents— one row per uploaded PDF: filename, purpose, chunk_count, status (processing/completed/failed). The actual vector embeddings live in Pinecone, not MySQL — this table is just the ledger.
- Frontend uses
@react-oauth/google'suseGoogleLogin(auth-code flow, scopeopenid email profile https://mail.google.com/) to get a Google authorization code. - Frontend POSTs the code to
POST /api/auth/google/callback. - Backend (
auth_service.googleLogin) exchanges the code for Googleaccess_token+refresh_token, fetches the Google profile, then:- If the email is new → creates a
usersrow and agmail_accountsrow (storing the refresh token) —registerUser. - If the email exists → updates the stored refresh token —
loginUser.
- If the email is new → creates a
- Backend mints its own JWT (
create_access_token) containing{user_id, name, email}with an expiry (ACCESS_TOKEN_EXPIRE_MINUTESenv var) and returns it. - Frontend stores the JWT in
localStorageunderaccess_tokenand attaches it as a Bearer token to every subsequent API call.GET /api/auth/useris used everywhere on the frontend as a lightweight "am I still logged in" probe.
Every Gmail operation on the backend re-derives a live Google access token from the
stored refresh token via GmailService._get_access_token() — there is no access
token caching layer, so each request does a token refresh call to Google.
gmail_service.py— the core wrapper around the Gmail API (googleapiclient). Responsibilities:- List/get messages & threads, fetch attachments.
- Build raw MIME messages (
_build_message) supporting To/Cc/Bcc, attachments, and threading headers (In-Reply-To/References) so replies stay in the same Gmail conversation. - Draft lifecycle: create/get/update/send/delete drafts; resolve a Gmail message id
to its draft id (
get_draft_id) since the rest of the app standardizes on message ids. - Reply/forward as immediate sends (
send_reply,send_forward) or as drafts in progress (create_reply_draft,update_reply_draft) — reply-all recipient logic excludes the sender and the signed-in user automatically. - Label/state mutations: read/unread toggle, star/unstar, archive/unarchive,
trash/untrash, spam/not-spam, permanent delete — each with a bulk (
batchModify) and single-message variant. - Gmail History API helpers (
get_latest_history_id,get_messages_since_history_id) used by the auto-reply scheduler to find only genuinely new messages since the last check. auto_reply_send— builds and sends a reply for the auto-reply pipeline.
Parser/— three parsers producing different shapes of an email for different UI needs:cardEmailParser(dashboard preview cards),shortEmailParser(list views: inbox/sent/drafts),fullEmailParser(full email + thread detail view).gmail.py— thin per-user helpers that look up the user's refresh token from MySQL, build aGmailService, and call it (get_5_emails,getEmailwith thread assembly,get_account,toggle_auto_reply).inbox_service.py,draft_service.py,sent_service.py,reply_service.py— the per-user, DB-token-lookup wrappers around specificGmailServiceoperations, called both by REST controllers and by the AI agent's tool layer.gmail_controller.py— the REST surface (/api/gmail/*): cards for the dashboard, inbox/sent/draft lists, single email + attachment download, draft CRUD + send, reply/forward/draft endpoints (multipart form-data with file attachments), star/read/ archive/trash/spam toggles (single + bulk), account info + auto-reply toggle, and the QALAM webhook.
Endpoint: POST /api/chatbot/ → chatbot_contoller.chat → run_chat_graph.
This is a small LangGraph state machine (agent_calling_graph.py):
_detect_intents(prompt)— simple keyword-based classifier over the user's message:- Gmail-ish keywords (email, mail, reply, draft, inbox, trash, spam, archive, ...)
→
"gmail"intent. - Knowledge-ish keywords (document, pdf, embed, knowledge, rag, chunk, ...)
→
"knowledge"intent. - Neither → falls back to
"retrieval"intent. - Both →
"multi"(fan-out to multiple agents and combine results).
- Gmail-ish keywords (email, mail, reply, draft, inbox, trash, spam, archive, ...)
→
- Graph nodes route to one of three specialized LangChain agents (built with
langchain.agents.create_agent, each its ownChatOpenAI(model="gpt-4.1-mini")instance bound to a curated tool list):gmail_agent(gmail_tools) — everything mailbox-related: get/search inbox, drafts, sent; create/update/send/delete drafts; reply/forward/send; star/read/ archive/trash/spam/delete; also has access to preference tools. Includes a regex fast-path (_maybe_send_direct_email) that detects simple "send an email to x@y.com saying ..." requests and sends them directly without invoking the LLM agent loop, to save latency/cost for the common case.knowledge_agent(knowledge_tools) — document retrieval (retrieval_from_docstool over Pinecone), document management, and preferences.RAG_agent.py— a generic retrieval agent (callagent) used for the"retrieval"fallback intent, and also exposesgenerate_auto_reply, the system prompt used specifically for the auto-reply pipeline (told to refuse/fallback rather than invent facts when it can't ground an answer in retrieved documents).
- Every agent call returns a normalized shape:
content,token_usage,backend_details(agent name, tools used),tool_calls, and serializedmessages. The LangGraph merges/accumulates these into a runningWorkflowstate (conversation history, agents/tools called, token totals) and returns it to the controller, which returns it straight to the frontend as JSON. - Conversation memory is per-
(user_id, date)via LangGraph's in-memoryMemorySavercheckpointer — so context resets daily and is not persisted across server restarts (despite thechat_threads/chat_messagestables existing in the schema).
Tool surface (agent/tools.py) is where LangChain @tool-decorated functions wrap
the underlying services (gmail, draft, reply, inbox, preferences, embeddings,
retrieval) — every mutating email tool call also writes an audit row via
log_ai_email_action (the same audit table that powers /api/stats/dashboard/gmail).
Structured generation (agent/llm/):
prompts.py— threePromptTemplates: draft generation, "should this email get an auto-reply?" (boolean), and "write the reply body" — each paired with a Pydantic output parser (objects.py/structures.py:DraftEmail,ShouldReply,ReplyEmail).llm_output_parsers.py— calls the LLM with those prompts and parses/repairs the JSON output (falls back gracefully if the model doesn't return perfectly valid JSON).error_handling.py— turns Azure/OpenAIBadRequestErrors (e.g. content-filter trips) into a clean, user-facing fallback message instead of a 500.
Designed to run on a schedule (schedular.py, APScheduler, every 10 minutes — currently
disabled in main.py), or can be invoked directly:
reply.py::autoReply()— finds everygmail_accountsrow withauto_reply = TRUEandstatus = 'ACTIVE'.reply_service.py::reply_mails_by_user()— for each such user:- First run (or if >24h since last check) just baselines the Gmail History id and does nothing else (avoids replying to the user's entire mailbox history on first enable).
- Otherwise, fetches messages since the last known
history_id(get_messages_since_history_id) and hands them to the filter.
filter.py::filter_and_send_messages()— for each new message not sent by the user themself:- Loads the user's preferences.
- Asks the LLM
shouldReply(email)— a strict "does this actually need a human-style reply?" classifier (deliberately says no to notifications, newsletters, receipts, OTPs, no-reply senders, spam, etc.). - If yes, generates a reply body with
createReplyEmail(LLM, given the email, the user's identity, and their preferences as hard constraints) and sends it immediately viagmail.auto_reply_send(threaded into the original conversation). - Every attempt (success or failure) is logged to
ai_email_actions.
- After processing, the account's
last_history_id/last_auto_reply_check_atare updated so the next run only looks at genuinely new mail.
- Upload (
POST /api/embed/, PDF only, frontend enforces ≤10MB): backend inserts adocumentsrow (status='processing'), extracts text (pypdf, with aPyPDF2fallback;.docxalso supported at the extraction-function level), splits it withRecursiveCharacterTextSplitter(chunk size 500 / overlap 50), embeds each chunk via the HuggingFace Inference API (sentence-transformers/all-MiniLM-L6-v2, mean-pooled to a 384-dim vector), and upserts into Pinecone under a per-user namespaceuser_id_{user_id}_documents, withmetadataincluding the source text (so retrieval doesn't need a second DB round-trip). Updates thedocumentsrow'schunk_count/statusafterward. - Delete — removes the Pinecone vectors for that doc's chunk-id range, then deletes
the
documentsrow. - List (
GET /api/embed/) — simple listing of a user's uploaded documents and their processing status, shown in the frontend's "Add knowledge" page. - Retrieval tool (
retrieval_from_docs) — a LangChain@toolused by both theknowledge_agentand the auto-reply'sgenerate_auto_replyprompt: embeds the query with the same HF model, does a top-5 similarity search scoped to the user's Pinecone namespace, and returns concatenated chunk text (or "No relevant documents were found").
Simple CRUD (GET/POST/PUT/PATCH/DELETE /api/preferences) over user_preferences. Each
preference is a free-text name + value + enabled flag. These are surfaced to every
AI agent (gmail, knowledge, retrieval, auto-reply) as tool-fetchable, hard-constraint
context — every system prompt explicitly instructs the LLM to treat preferences as
non-negotiable rather than optional style hints.
GET /api/search/gmail?q=... performs a live Gmail API search
(messages.list with the Gmail query string), fetches full message bodies for the
results, and re-scores/filters them locally by simple term-matching against subject/
preview/body/participants (_score_email) before returning a sorted, capped list. Every
search is logged to search_queries (log_search_query), and GET /api/search/recent
returns the user's last few distinct queries for quick re-run suggestions.
GET /api/stats/dashboard/gmail — three numbers computed from ai_email_actions:
total AI actions, total auto-replies sent (reply_generated + completed), and
"others" (the remainder) — rendered as colored stat pills on the frontend dashboard.
- The APScheduler auto-reply job is commented out in
main.py's startup/shutdown hooks — auto-reply as a continuously-running background feature is not currently wired to actually fire on its own; the code paths exist and work if invoked. - There are two duplicate/typo'd venv folders (
venv/,vevn/) inbackend/— likely leftover from a rename; only one is presumably the active interpreter. - Two of the same email-list functions exist with slightly different call styles across
files (a
parse_recipientshelper is defined identically in bothapp/gmail/DTO.pyand inline insidegmail_controller.updateDraft). - Refresh tokens are stored as plain
TEXTin MySQL (gmail_accounts.refresh_token) — no column-level encryption is applied in the code read. chat_threads/chat_messagestables exist in the SQL schema but the live chat implementation does not currently write to them (conversation memory is process-local via LangGraph'sMemorySaver).
- Next.js 16 (App Router,
app/directory), React 19. - Tailwind CSS 4 for styling (utility classes throughout, Gmail-like color palette:
#1a73e8blue,#ea4335red,#34a853green,#fbbc05amber). @react-oauth/googlefor the Google sign-in button/flow.lucide-reactfor icons,rechartsavailable (dashboard/stat visuals).- No global state library — each page/component manages its own
useState/useEffectand talks directly to the backend viafetch. - Auth model: JWT stored in
localStorage.access_token; every page/component that needs auth callsGET /api/auth/useron mount to verify the token and fetch the profile, redirecting to/login(or/) on failure. There is no shared auth context provider — this check is duplicated per page/component (Sidebar,page.js,inbox,chat,settings, etc.). - Backend base URL comes from
NEXT_PUBLIC_API_BASE_URL(defaults tohttp://localhost:8000if unset).
| Route | File | Purpose |
|---|---|---|
/ |
page.js |
Root: checks auth, renders Home (dashboard) if logged in, else Nologin (redirects to /login) |
/login |
login/page.js |
Google sign-in screen with marketing copy + OAuth button |
/inbox |
inbox/page.js |
Gmail-like inbox list: filters (all/unread/starred/important/attachments/inbox/sent/draft), bulk select, bulk trash/delete, per-row nav to /email/{id}. Includes AgentAssistant floating chat widget |
/sent |
sent/page.js |
Sent items list (same pattern) |
/drafts |
drafts/page.js |
Draft list — likely supports editing/sending drafts in place (NewDraftPopup) |
/email/[id] |
email/[id]/page.js |
Full email + thread detail view; reply/forward via ReplyBox |
/chat |
chat/page.js |
Full-screen dedicated AI chat UI (dark theme) talking to POST /api/chatbot |
/search |
search/page.js |
Gmail search UI backed by /api/search/gmail and /api/search/recent |
/source |
source/page.js |
"Add knowledge" — PDF upload → embeddings management UI (list/upload/delete against /api/embed) |
/settings |
settings/page.js |
Tabbed settings: Profile / Gmail Account / AI Preferences, plus logout (with a warning that logout disables auto-reply) |
/profile |
profile/page.js |
User profile view/edit |
/privacy, /terms |
static pages | Legal copy linked from the login screen |
layout.js |
— | Root layout: wraps the app in GoogleOAuthProvider, renders the persistent Sidebar + main content area |
Sidebar.jsx— persistent left nav (collapsible, mobile drawer variant). Performs its own/api/auth/usercheck and renders nothing if unauthenticated (so it disappears on the login page). Shows Dashboard / Add knowledge / Inbox / Sent / Draft / AI Assistant / Search / Settings, a Compose button, and the signed-in user's avatar initials + logout.Home.jsx— the authenticated dashboard: welcome header, quick-nav cards (Inbox, Sent, Drafts, "Chat with AI"), live stat pills from/api/stats/dashboard/gmail, per-category preview cards from/api/gmail/card(last received/sent/drafts/ important), and embeds the fullPreferencespage inline at the bottom.AgentAssistant.jsx— a reusable floating chat widget (bottom-right panel) embedded on content pages (inbox, etc.) that talks toPOST /api/chatbot, shows suggested quick-action buttons, and surfaces "response diagnostics" (token usage, tools called, which agent handled the request) for transparency/debugging.MessageCard.jsx— renders one of the dashboard's category preview cards (inbox/ sent/drafts/important) with its list of recent emails.ReplyBox.jsx— the reply/forward composer used on the email detail page; talks to the reply/forward/draft multipart endpoints described in §3.5.NewDraftPopup.jsx— compose-new / edit-draft modal, supports AI-assisted ("mode: ai") vs manual draft creation viaPOST /api/gmail/draft/create.Preferences.jsx— full CRUD UI foruser_preferences(search/filter by status, create/edit modal, enable/disable toggle switch, delete with confirmation) — used both standalone (Settings → AI Preferences) and embedded on the dashboard.Gmail.jsx— "Gmail Account" settings panel (connection status, auto-reply toggle, presumably callsPATCH /api/gmail/account/auto-reply).Profile.jsx— profile settings panel.Nologin.jsx— redirect-only stub that sends unauthenticated visitors to/login.ActionPill.jsx,Chip.jsx,DetailRow.jsx— small shared presentational primitives (badges/pills/detail rows) used across the email list/detail views.
- All list/detail Gmail data flows through the backend's parsers (
Card_email_parser,Short_email_parser,parse_email_full) rather than raw Gmail API payloads, so the frontend gets consistent shapes (subject,from,to/cc,preview/body,labels,starred,unread,attachments, etc.). - Reply/forward/draft endpoints are intentionally all multipart/form-data (even with
no attachments) for consistency, per explicit comments in
gmail_controller.py. - The chat endpoints (
/api/chatbot) return a rich payload (content,token_usage,tool_calls,backend_details,agents_called) that the frontend chat UIs use to show "diagnostics" (which agent/tools handled the request, token counts) alongside the plain-text reply.
Backend (backend/.env)
GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET,REDIRECT_URI— Google OAuth app config.SECRET_KEY,ALGORITHM,ACCESS_TOKEN_EXPIRE_MINUTES— JWT signing config.DB_HOST,DB_PORT,DB_USER,DB_PASSWORD,DB_NAME— MySQL connection.HF_TOKEN,HF_MODEL(defaultsentence-transformers/all-MiniLM-L6-v2) — HuggingFace Inference API for embeddings.PINECONE_API_KEY,PINECONE_INDEX_NAME— vector store.EMAIL_VERIFICATION_API_KEY— static bearer-style key protecting the QALAM webhook.- LLM base URL is currently hard-coded in source
(
https://openai-rg-nadeem.openai.azure.com/openai/v1, modelgpt-4.1-mini) rather than read from an env var — the API key for this presumably comes from a standardOPENAI_API_KEYpicked up implicitly bylangchain_openai.ChatOpenAI.
Frontend (frontend/.env)
NEXT_PUBLIC_API_BASE_URL— backend base URL (defaults tohttp://localhost:8000).NEXT_PUBLIC_GOOGLE_CLIENT_ID— Google OAuth client id used byGoogleOAuthProvider/useGoogleLogin.
This is a genuinely full-featured agentic Gmail assistant, not a toy demo:
- Real Gmail read/write access via OAuth, covering essentially every mailbox action a webmail client needs.
- A proper multi-agent LLM architecture (LangGraph router → specialized LangChain tool-calling agents) rather than a single monolithic prompt, with structured-output parsing and graceful error handling for content-filter/LLM failures.
- A working RAG pipeline (PDF → chunks → HF embeddings → Pinecone) feeding both the interactive chat assistant and the fully autonomous auto-reply pipeline.
- User-controllable "preferences" that are threaded through every agent as binding
constraints, plus a full audit trail (
ai_email_actions) of every AI-performed mailbox action, surfaced back to the user as dashboard stats. - A polished, Gmail-styled Next.js frontend covering the full CRUD surface (inbox, sent, drafts, compose/reply/forward, search, settings, document knowledge management) plus a dedicated full-screen AI chat page and an embeddable floating assistant widget.
The main things flagged as "unfinished/rough edges" rather than fundamental issues: the
auto-reply scheduler is currently disabled at startup, chat history persistence tables
exist but aren't wired up (memory is in-process only), and there's some minor code
duplication/leftover artifacts (duplicate venv folder, a parse_recipients helper
defined twice).
Gmail Assistant Agent has been published as a live, user-facing App on Anna AI OS, qualifying it for the Anna AI App Builder Program (via DoraHacks).
-
Environment setup & developer identity
- Configured the local environment with
uv(Python manager) andNode.js v22. - Established a developer namespace on Anna under the handle
@nadeemmushtaq. - Authenticated the local environment with the Anna Partners network via device-flow login.
- Configured the local environment with
-
App scaffolding & manifest configuration
- Initialized the project with the Anna App CLI (
anna-app). - Wrote a custom
manifest.json(schema v2) defining the app's identity, permissions, and UI behavior. - Implemented a remote-URL bridge so the app loads the live-deployed Next.js
frontend (
https://gmail-assistant-eight.vercel.app) inside the secure Anna sandbox.
- Initialized the project with the Anna App CLI (
-
Validation & publishing
- Passed schema and ACL validation via the
anna-app validatesuite. - Staged and cut version 0.1.0, uploading the app bundle to Anna's infrastructure.
- Published the app to the Anna Marketplace.
- Passed schema and ACL validation via the
https://anna.partners/@nadeemmushtaq/gmail-assistant-agent
- Fully runnable — a live, functional app inside Anna OS, not just a repository.
- User-facing interface — the existing Next.js UI, running inside the Anna sandbox.
- Backend logic — the same Python/FastAPI agentic backend described above.
- Ready for MAU — prepared for Qualified App Runs via Anna's native execution infrastructure, for the Anna AI App Builder Program's September Launch Boost.