QDG Knowledge Base Read-only viewer QWebHub
general

API Reference

Version 1 · Initial API reference documenting live routes only, with dead/unregistered blueprints called out separately

SmartMail API Reference

Single Flask app (app/main.py). Auth model: a session cookie (session["logged_in"], set by /login) gates almost nothing except /logout and the GPT Prompt admin endpoints; everything else that touches the mailbox is gated by a Microsoft Graph token (get_access_token_silent(), returns 401 JSON if missing) — these are two unrelated auth layers, see User Guide. Routes below marked "token-gated" require the Graph token; "session-gated" require the app login.

Important: only blueprints passed to app.register_blueprint() in main.py are reachable. Several route files define blueprints that are imported but never registered, or shadow themselves — see "Not currently reachable" at the end. Don't assume a route exists just because its handler is defined somewhere in app/routes/.

Auth / Login

  • GET/POST / and /login — login form (hardcoded credentials, see User Guide) — no auth
  • GET /logout — clears session — requires login
  • GET /api/auth-status — {authenticated: bool} based on cached Graph token — no auth

Pages

  • GET /outlook — the Outlook-style SPA shell — no auth
  • GET /admin — admin dashboard shell (registered twice, admin_page/admin_ui — harmless duplicate, same template) — no auth
  • GET /dashboard — SQL-backed metrics dashboard — no auth
  • GET /trace/<trace_id> — ordered audit-log steps for a trace id — no auth
  • GET /inbox, /folder/<name> — legacy server-rendered folder view — token-gated (⚠ raises SystemExit on missing token, not just 401 — avoid hitting unauthenticated in production)

Inbox / Folders

  • GET /api/folders — folder list with unread/total counts — token-gated
  • GET /api/emails/<folder_name> — paginated/searchable message list (top=all, q=) — token-gated
  • GET /api/email/<message_id> — full message detail (to/cc/bcc, attachments, draft/sent/inbox) — token-gated
  • GET /api/conversation-counts — {conversationId: messageCount} — token-gated
  • POST /api/mark-read/<message_id> — token-gated
  • POST /api/move/<message_id> — body {dest_folder_id} — token-gated
  • POST /api/messages/<message_id>/delete — moves to Deleted Items — token-gated
  • POST /api/messages/<message_id>/archive — moves to Archive — token-gated
  • GET /api/email-download/<message_id> — ⚠ broken: col is never bound to a real collection, will raise on any real call

Threads

  • GET /api/thread/<message_id> — full conversation across inbox/sent/drafts — token-gated
  • POST /api/threads/<thread_id>/update-summary — refresh rolling Mongo thread summary
  • POST /api/draft/from-summary — draft body text from rolling summary (does not create a Graph draft)
  • GET /api/thread-summary/<message_id> — SQL-backed conversation summary with bullet points — token-gated

Compose / Drafts

  • POST /api/draft/generate — the core AI reply-draft endpoint (reuses/creates a Graph draft; honours per-category "no reply" mode) — token-gated
  • POST /api/draft/update — patch an existing draft's HTML body — token-gated
  • POST /api/draft/send — send an existing draft — token-gated
  • GET /api/draft/find — find an existing reply draft for an email id — token-gated
  • POST /api/compose/send — direct send (To/Subject/Body) — ⚠ broken: references undefined message/cc_list/bcc_list, will NameError on a real request; CC/BCC aren't wired into the Compose UI anyway

Categories

  • GET /api/categories — minimal {items:[{name}]} for UI dropdowns — no auth
  • GET /api/admin/categories — full list — no auth
  • POST /api/admin/categories — create (re-runs recategorisation) — no auth
  • PUT /api/admin/categories/<name> — rename/update (re-runs recategorisation) — no auth
  • DELETE /api/admin/categories/<name> — delete (resets affected emails to unknown) — no auth
  • GET/POST /api/admin/categories/<name>/prompt — read/write a category's classification rule text — no auth

GPT Prompts (session-gated — the one place login is actually enforced)

  • GET /api/admin/gpt-prompts — list general prompts
  • GET/PUT /api/admin/gpt-prompts/<name> — fetch/save a named prompt
  • DELETE /api/admin/gpt-prompts/<name> — delete a named prompt
  • GET/PUT /api/admin/gpt-prompts/category/<category> — fetch (auto-creates default)/save a category's reply-drafting prompt
  • DELETE /api/admin/gpt-prompts/category/<category> — reset to default

People

  • GET /api/people?q=<text> — Graph People/Contacts typeahead (min 2 chars) for Compose's To field — token-gated

MongoDB data / admin

  • GET /api/mongodb/email_summaries — paginated/filterable list (search, direction, category) — no auth
  • GET/PUT/DELETE /api/mongodb/email_summaries/<message_id> — fetch/update/delete a summary doc — no auth
  • POST /api/mongodb/email_summaries/<message_id>/pin — toggle pinned — no auth
  • PUT /api/email/<message_id>/category — set category on a summary doc — no auth
  • GET /api/admin/mongodb/emails — raw emails collection listing (separate connection) — no auth
  • GET /admin/mongodb-latest — 20 most recent summarised emails — no auth
  • GET /api/admin/users — list admin users — no auth
  • POST /api/admin/users — create an admin user — no auth (note: this user list is unrelated to the credentials the login form actually checks — see User Guide)
  • POST /api/save-summary-direct — "open message" trigger: strips signature/disclaimer, calls the LLM summariser, enriches recipients, caches raw + summary — Graph-enrichment step is token-gated but the rest works degraded without one

Automation / Webhooks

  • GET/POST /graph/webhook — Graph subscription validation + notification handler — no auth (Graph calls it). ⚠ Provisioned but inert: nothing in the codebase ever calls create_subscription(), so Microsoft Graph never actually sends this endpoint anything today.

CLI (not HTTP)

python -m app.main <command> exposes login, me, inbox, folders, mail, read, search, unread, paginate, attachments, extract-text, list-attachments, init-db, create-draft, update-draft, send-draft, create-folder, move, mark-read, summarise, classify, extract, reply, gpt-summarise, gpt-classify, gpt-kv-extract, gpt-reply-draft, poll, subscribe, dashboard, list-emails, web. See CLI Quick Reference.

Not currently reachable (defined in source, never registered/shadowed — do not rely on these)

  • app/routes/inbox_api.py (bp_inbox, GET /api/inbox) — imported and even extended with /api/mongodb/recent-inbox in main.py, but never register_blueprint()'d.
  • app/routes/category_api.py (bp_category_api) — full category CRUD, never registered (the live category endpoints above are implemented separately, directly in main.py).
  • app/routes/category_prompt_api.py — defines three Blueprint objects reusing the same variable name; only the last (a broken /admin/categories form handler referencing undefined names) is the one main.py actually imports and registers. Its real /api/categories GET/PUT routes are shadowed and never mounted.
  • app/routes/admin_routes.py (admin_bp) — never registered; also defines GET /api/admin/emails twice in the same file (second silently overwrites the first).
  • app/routes/mongodb_admin_api.py (bp_mongo_admin) — a cleaner whitelisted-field CRUD for email_summaries, never registered (the live version above is less strict about fields).
  • app/routes/inbox_routes.py, app/routes/mongodb_routes.py, app/routes/email_routes.py — only commented-out example code, no live or dead routes.
  • app/routes_thread.py (bp, GET /api/thread/<message_id>) — never registered; superseded by the live api_thread in main.py.
  • app/web_app.py — a completely separate Flask instance with its own GET /admin; only runs if executed directly (python app/web_app.py), never imported by main.py — not part of the running server.
  • main.py's own second /graph/webhook handler (webhook, registered after automation_bp's) — shadowed by the earlier registration; effectively dead.
Updated by Claude on Aug. 12, 2026, 9:07 a.m. · Task: updatewiki create project documentation (API)