From eee315fe3dab964919bce78a481e1b89dd0c7a9b Mon Sep 17 00:00:00 2001 From: Sean O'Connor Date: Sun, 2 Aug 2026 16:07:16 -0400 Subject: [PATCH] fix read-only behavior --- src/server.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/server.py b/src/server.py index 3f30a51..3eda2f6 100644 --- a/src/server.py +++ b/src/server.py @@ -225,6 +225,12 @@ def parse_accounts(config: dict) -> list[dict]: def resolve_account(accounts: list[dict], account_id: str) -> dict: + # No ambiguity possible with a single configured account — use it + # even if the caller passed an account_id that doesn't match (e.g. a + # hallucinated or stale id), rather than hard-failing. + if len(accounts) == 1: + return accounts[0] + if not account_id or not account_id.strip(): raise ValueError( f"account_id is required. Available accounts: {[a['id'] for a in accounts]}. " @@ -479,10 +485,14 @@ async def _forward_uid_batch( len(uids), _format_uid_list(uids), ) - raw_messages = await asyncio.to_thread(client.fetch, uids, ["RFC822"]) + # BODY.PEEK[] (not RFC822/BODY[]) so the fetch never implicitly sets + # \Seen — some servers (e.g. iCloud) apply \Seen on a plain RFC822 + # fetch even when the folder was SELECTed read-only, which then queues + # an unsolicited FETCH FLAGS response that breaks the next IDLE call. + raw_messages = await asyncio.to_thread(client.fetch, uids, ["BODY.PEEK[]"]) for uid in uids: data = raw_messages.get(uid, {}) - raw = data.get(b"RFC822", b"") + raw = data.get(b"BODY[]", b"") if not raw: logger.debug( "[%s/%s] Skipping UID %s because RFC822 payload was empty", @@ -912,10 +922,10 @@ async def read_email( client = get_imap_client(acc) try: client.select_folder(folder, readonly=True) - data = client.fetch([uid], ["RFC822"]) + data = client.fetch([uid], ["BODY.PEEK[]"]) if uid not in data: return {"error": f"Email UID {uid} not found in {folder}"} - return parse_email_message(data[uid][b"RFC822"]) + return parse_email_message(data[uid][b"BODY[]"]) finally: client.logout()