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 authGET /logout— clears session — requires loginGET /api/auth-status—{authenticated: bool}based on cached Graph token — no auth
Pages
GET /outlook— the Outlook-style SPA shell — no authGET /admin— admin dashboard shell (registered twice,admin_page/admin_ui— harmless duplicate, same template) — no authGET /dashboard— SQL-backed metrics dashboard — no authGET /trace/<trace_id>— ordered audit-log steps for a trace id — no authGET /inbox,/folder/<name>— legacy server-rendered folder view — token-gated (⚠ raisesSystemExiton missing token, not just 401 — avoid hitting unauthenticated in production)
Inbox / Folders
GET /api/folders— folder list with unread/total counts — token-gatedGET /api/emails/<folder_name>— paginated/searchable message list (top=all,q=) — token-gatedGET /api/email/<message_id>— full message detail (to/cc/bcc, attachments, draft/sent/inbox) — token-gatedGET /api/conversation-counts—{conversationId: messageCount}— token-gatedPOST /api/mark-read/<message_id>— token-gatedPOST /api/move/<message_id>— body{dest_folder_id}— token-gatedPOST /api/messages/<message_id>/delete— moves to Deleted Items — token-gatedPOST /api/messages/<message_id>/archive— moves to Archive — token-gatedGET /api/email-download/<message_id>— ⚠ broken:colis 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-gatedPOST /api/threads/<thread_id>/update-summary— refresh rolling Mongo thread summaryPOST /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-gatedPOST /api/draft/update— patch an existing draft's HTML body — token-gatedPOST /api/draft/send— send an existing draft — token-gatedGET /api/draft/find— find an existing reply draft for an email id — token-gatedPOST /api/compose/send— direct send (To/Subject/Body) — ⚠ broken: references undefinedmessage/cc_list/bcc_list, willNameErroron a real request; CC/BCC aren't wired into the Compose UI anyway
Categories
GET /api/categories— minimal{items:[{name}]}for UI dropdowns — no authGET /api/admin/categories— full list — no authPOST /api/admin/categories— create (re-runs recategorisation) — no authPUT /api/admin/categories/<name>— rename/update (re-runs recategorisation) — no authDELETE /api/admin/categories/<name>— delete (resets affected emails tounknown) — no authGET/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 promptsGET/PUT /api/admin/gpt-prompts/<name>— fetch/save a named promptDELETE /api/admin/gpt-prompts/<name>— delete a named promptGET/PUT /api/admin/gpt-prompts/category/<category>— fetch (auto-creates default)/save a category's reply-drafting promptDELETE /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 authGET/PUT/DELETE /api/mongodb/email_summaries/<message_id>— fetch/update/delete a summary doc — no authPOST /api/mongodb/email_summaries/<message_id>/pin— toggle pinned — no authPUT /api/email/<message_id>/category— set category on a summary doc — no authGET /api/admin/mongodb/emails— rawemailscollection listing (separate connection) — no authGET /admin/mongodb-latest— 20 most recent summarised emails — no authGET /api/admin/users— list admin users — no authPOST /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 callscreate_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-inboxinmain.py, but neverregister_blueprint()'d.app/routes/category_api.py(bp_category_api) — full category CRUD, never registered (the live category endpoints above are implemented separately, directly inmain.py).app/routes/category_prompt_api.py— defines threeBlueprintobjects reusing the same variable name; only the last (a broken/admin/categoriesform handler referencing undefined names) is the onemain.pyactually imports and registers. Its real/api/categoriesGET/PUT routes are shadowed and never mounted.app/routes/admin_routes.py(admin_bp) — never registered; also definesGET /api/admin/emailstwice in the same file (second silently overwrites the first).app/routes/mongodb_admin_api.py(bp_mongo_admin) — a cleaner whitelisted-field CRUD foremail_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 liveapi_threadinmain.py.app/web_app.py— a completely separateFlaskinstance with its ownGET /admin; only runs if executed directly (python app/web_app.py), never imported bymain.py— not part of the running server.main.py's own second/graph/webhookhandler (webhook, registered afterautomation_bp's) — shadowed by the earlier registration; effectively dead.