Adopt cursor/delta contract for mailbox message lists #9

Closed
opened 2026-07-09 16:22:10 +02:00 by zemion · 2 comments
Owner

The mailbox message list still uses offset pagination against IMAP-backed folders (GET /api/v1/mail/profiles/{profile_id}/mailbox/messages). Before enabling row-level deltas here, the endpoint needs provider-aware cursor semantics based on stable mailbox state, for example folder + UIDVALIDITY + UID anchors, with a fallback to full refresh when the server cannot guarantee stability.

Scope:

  • Replace offset-only paging with opaque cursor support while keeping offset compatibility for existing clients.
  • Include folder/profile/query inputs in the cursor fingerprint.
  • Return cursor and next_cursor in the list response.
  • Define when deltas are safe; for unsupported IMAP states, return full snapshots and rely on private ETags.
  • Update MailboxPage to prefer cursor-backed next/previous paging.
  • Add tests using mock mailbox records and at least one unstable-cursor fallback case.

Related platform work: govoplan-core#222 and the shared govoplan_core.core.pagination cursor helpers.

The mailbox message list still uses offset pagination against IMAP-backed folders (`GET /api/v1/mail/profiles/{profile_id}/mailbox/messages`). Before enabling row-level deltas here, the endpoint needs provider-aware cursor semantics based on stable mailbox state, for example folder + UIDVALIDITY + UID anchors, with a fallback to full refresh when the server cannot guarantee stability. Scope: - Replace offset-only paging with opaque cursor support while keeping offset compatibility for existing clients. - Include folder/profile/query inputs in the cursor fingerprint. - Return `cursor` and `next_cursor` in the list response. - Define when deltas are safe; for unsupported IMAP states, return full snapshots and rely on private ETags. - Update `MailboxPage` to prefer cursor-backed next/previous paging. - Add tests using mock mailbox records and at least one unstable-cursor fallback case. Related platform work: govoplan-core#222 and the shared `govoplan_core.core.pagination` cursor helpers.
Author
Owner

Partial implementation landed; keeping this open because true IMAP UID-range/delta semantics still need a protocol-specific pass.

Done now:

  • Added opaque cursor support to GET /api/v1/mail/profiles/{profile_id}/mailbox/messages while keeping offset compatibility.
  • Cursor fingerprints include tenant/profile/folder/limit and carry UIDVALIDITY/total-count state when available.
  • Unsupported or changed mailbox state falls back to a first-page snapshot marked full.
  • Mock IMAP exposes stable UIDVALIDITY for cursor tests.
  • MailboxPage now stores next-page cursors and prefers them for sequential navigation, with offset fallback for random jumps.
  • Added smoke coverage for mock mailbox cursor continuation and cursor mismatch rejection.

Still open:

  • Replace the offset-compatible bridge with real UID-anchor/range paging where IMAP servers support it.
  • Add an explicit unstable-cursor fallback test that simulates UIDVALIDITY or equivalent mailbox-state change.
  • Define any future row-level mailbox delta endpoint only for providers with safe revision/deletion semantics.

Verification: test_mailbox_message_listing_reports_total_count passes.

Partial implementation landed; keeping this open because true IMAP UID-range/delta semantics still need a protocol-specific pass. Done now: - Added opaque cursor support to `GET /api/v1/mail/profiles/{profile_id}/mailbox/messages` while keeping offset compatibility. - Cursor fingerprints include tenant/profile/folder/limit and carry UIDVALIDITY/total-count state when available. - Unsupported or changed mailbox state falls back to a first-page snapshot marked `full`. - Mock IMAP exposes stable UIDVALIDITY for cursor tests. - MailboxPage now stores next-page cursors and prefers them for sequential navigation, with offset fallback for random jumps. - Added smoke coverage for mock mailbox cursor continuation and cursor mismatch rejection. Still open: - Replace the offset-compatible bridge with real UID-anchor/range paging where IMAP servers support it. - Add an explicit unstable-cursor fallback test that simulates UIDVALIDITY or equivalent mailbox-state change. - Define any future row-level mailbox delta endpoint only for providers with safe revision/deletion semantics. Verification: `test_mailbox_message_listing_reports_total_count` passes.
Author
Owner

Codex State: done

Summary

  • Mailbox cursors now use IMAP UID anchors with UIDVALIDITY instead of sequence-offset bridge semantics.
  • Real IMAP listing pages via UID SEARCH/FETCH, while mock IMAP follows the same anchor contract.
  • Stale UIDVALIDITY or missing anchor resets to a full first page and reports full=true.

Changed Files

  • src/govoplan_mail/backend/sending/imap.py
  • src/govoplan_mail/backend/router.py
  • tests/test_api_smoke.py

Verification

  • .venv/bin/python -m unittest tests.test_api_smoke.ApiSmokeTests.test_mailbox_message_listing_reports_total_count
  • npm run build (govoplan-core/webui)
## Codex State: done ### Summary - Mailbox cursors now use IMAP UID anchors with UIDVALIDITY instead of sequence-offset bridge semantics. - Real IMAP listing pages via UID SEARCH/FETCH, while mock IMAP follows the same anchor contract. - Stale UIDVALIDITY or missing anchor resets to a full first page and reports full=true. ### Changed Files - `src/govoplan_mail/backend/sending/imap.py` - `src/govoplan_mail/backend/router.py` - `tests/test_api_smoke.py` ### Verification - `.venv/bin/python -m unittest tests.test_api_smoke.ApiSmokeTests.test_mailbox_message_listing_reports_total_count` - `npm run build (govoplan-core/webui)`
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: add-ideas/govoplan-mail#9
No description provided.