From 21ff44e9e8b5d19f253f556efedd3cd5f1f2c2b6 Mon Sep 17 00:00:00 2001 From: els-hub Date: Mon, 15 Jun 2026 08:54:13 +0300 Subject: [PATCH] perf(email): run blocking IMAP routes in threadpool Fixes #4232 Convert email search and archive handlers from async def to sync def so FastAPI runs their blocking IMAP I/O in the threadpool instead of the event loop. --- routes/email_routes.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/routes/email_routes.py b/routes/email_routes.py index f8ad50e2e..c38cf6a84 100644 --- a/routes/email_routes.py +++ b/routes/email_routes.py @@ -1087,7 +1087,10 @@ def setup_email_routes(): return {"contacts": [], "error": "Mail operation failed"} @router.get("/search") - async def search_emails( + # Sync def: the body is blocking IMAP I/O with no awaits. As `async def` it ran + # directly on the event loop and stalled the whole app during a search; as a sync + # def FastAPI runs it in a threadpool, keeping the loop responsive. + def search_emails( q: str = Query(""), folder: str = Query("INBOX"), limit: int = Query(50), @@ -1736,7 +1739,9 @@ def setup_email_routes(): return {"success": False, "error": "Mail operation failed"} @router.post("/archive/{uid}") - async def archive_email(uid: str, folder: str = Query("INBOX"), account_id: str | None = Query(None), owner: str = Depends(require_owner)): + # Sync def: blocking IMAP I/O with no awaits — see search_emails above. Runs in a + # threadpool instead of blocking the event loop. + def archive_email(uid: str, folder: str = Query("INBOX"), account_id: str | None = Query(None), owner: str = Depends(require_owner)): """Move email to Archive folder.""" try: with _imap(account_id, owner=owner) as conn: