Stable uid on IMAP #539

Open
opened 2026-06-08 06:10:16 +00:00 by guettli · 5 comments
guettli commented 2026-06-08 06:10:16 +00:00 (Migrated from codeberg.org)

View and comment on the plan

When a message gets moved in IMAP, then the old uid gets lost.

Afaik there is no reliable way to address the mail in the new folder. Research that. Is there a way?

We could do that trick: we create a DB table of expected new mails in a folder. On next sync the moved message appears as new. When the massageID matches, then we can update the uid.

Then we can update the UndoLog

Does that make sense?



Agentloop runs

# Started (UTC) Ended (UTC) Status Tokens in Tokens out
1 2026-06-08 06:14:28 2026-06-08 06:19:16 success 21 8 475
2 2026-06-10 13:00:59 2026-06-10 13:25:46 success 124 39 189
3 2026-06-10 14:19:51 2026-06-10 14:21:50 failed
4 2026-06-10 17:44:33 2026-06-10 17:59:32 success 40 9 431
<!-- agentloop:plan --> [View and comment on the plan](https://codeberg.org/guettli/sharedinbox/pulls/541/files) <!-- /agentloop:plan --> When a message gets moved in IMAP, then the old uid gets lost. Afaik there is no reliable way to address the mail in the new folder. Research that. Is there a way? We could do that trick: we create a DB table of expected new mails in a folder. On next sync the moved message appears as new. When the massageID matches, then we can update the uid. Then we can update the UndoLog Does that make sense? --- --- <!-- agentloop-summary --> **Agentloop runs** | # | Started (UTC) | Ended (UTC) | Status | Tokens in | Tokens out | |---|---------------------|---------------------|---------|-----------|------------| | 1 | 2026-06-08 06:14:28 | 2026-06-08 06:19:16 | success | 21 | 8 475 | | 2 | 2026-06-10 13:00:59 | 2026-06-10 13:25:46 | success | 124 | 39 189 | | 3 | 2026-06-10 14:19:51 | 2026-06-10 14:21:50 | failed | — | — | | 4 | 2026-06-10 17:44:33 | 2026-06-10 17:59:32 | success | 40 | 9 431 |
guettlibot commented 2026-06-10 13:25:46 +00:00 (Migrated from codeberg.org)

PR opened: https://codeberg.org/guettli/sharedinbox/pulls/558

Summary

Implemented stable IMAP identity across MOVE operations. The fix uses the RFC 4315 COPYUID response code that modern UIDPLUS servers attach to MOVE/COPY (already exposed by enough_mail), with a UID SEARCH HEADER Message-ID fallback for servers without UIDPLUS.

What changed:

  • _applyPendingChangeImap now captures the result of every uidMove call and feeds it into a new _remapEmailAfterImapMove helper.
  • The helper resolves the destination UID (COPYUID → header search) and atomically rewrites the row id from accountId:srcMailbox:srcUid to accountId:destMailbox:newUid, cascading the change to email_bodies, threads, pending_changes, and undo_actions so cached bodies and queued undo entries keep tracking the message.
  • _reconcileDeletedImap now skips rows whose move/snooze/unsnooze is still in pending_changes, fixing a latent race where the optimistically-moved row could be wiped before the server flush ran.
  • SYNC.md and DB-SYNC.md document the new behavior.

This is simpler than the "predicted mail" table sketched in the issue: the response code path is reliable and the search fallback is bounded and stateless — no new schema needed.

Verified via flutter analyze (clean) and flutter test test/unit (all 382 unit tests pass, including 4 new tests covering COPYUID remap, header-search fallback, undo_actions rewrite, and the reconciliation guard). Backend tests need a real Stalwart instance and will be exercised by CI.

PR: https://codeberg.org/guettli/sharedinbox/pulls/558

agentloop · agent: claude

PR opened: https://codeberg.org/guettli/sharedinbox/pulls/558 ## Summary Implemented stable IMAP identity across `MOVE` operations. The fix uses the **RFC 4315 `COPYUID`** response code that modern UIDPLUS servers attach to `MOVE`/`COPY` (already exposed by `enough_mail`), with a **`UID SEARCH HEADER Message-ID`** fallback for servers without UIDPLUS. What changed: - `_applyPendingChangeImap` now captures the result of every `uidMove` call and feeds it into a new `_remapEmailAfterImapMove` helper. - The helper resolves the destination UID (COPYUID → header search) and atomically rewrites the row id from `accountId:srcMailbox:srcUid` to `accountId:destMailbox:newUid`, cascading the change to `email_bodies`, `threads`, `pending_changes`, and `undo_actions` so cached bodies and queued undo entries keep tracking the message. - `_reconcileDeletedImap` now skips rows whose `move`/`snooze`/`unsnooze` is still in `pending_changes`, fixing a latent race where the optimistically-moved row could be wiped before the server flush ran. - `SYNC.md` and `DB-SYNC.md` document the new behavior. This is simpler than the "predicted mail" table sketched in the issue: the response code path is reliable and the search fallback is bounded and stateless — no new schema needed. Verified via `flutter analyze` (clean) and `flutter test test/unit` (all 382 unit tests pass, including 4 new tests covering COPYUID remap, header-search fallback, undo_actions rewrite, and the reconciliation guard). Backend tests need a real Stalwart instance and will be exercised by CI. PR: https://codeberg.org/guettli/sharedinbox/pulls/558 <sub>agentloop · agent: claude</sub>
guettlibot commented 2026-06-10 14:19:53 +00:00 (Migrated from codeberg.org)

agentloop: CI failed on PR #558; resuming acpx session to fix (attempt 1/3).

agentloop: CI failed on PR #558; resuming acpx session to fix (attempt 1/3).
guettlibot commented 2026-06-10 14:21:51 +00:00 (Migrated from codeberg.org)

agentloop: agent hit the Anthropic quota; pausing this loop until 2026-06-10T17:40:00Z and will retry then. (Internal error: You're out of extra usage · resets 5:40pm (UTC) (account: tilldu@googlemail.com))

agentloop: agent hit the Anthropic quota; pausing this loop until 2026-06-10T17:40:00Z and will retry then. (Internal error: You're out of extra usage · resets 5:40pm (UTC) (account: tilldu@googlemail.com))
guettlibot commented 2026-06-10 17:54:32 +00:00 (Migrated from codeberg.org)

Pushed the fix to the PR.

CI was failing on dart analyze --fatal-infos (stricter than my local flutter analyze), specifically require_trailing_commas on a map literal in the new "no UIDPLUS" test. Pulled the literal into a const map with a trailing comma. Verified locally with flutter analyze --fatal-infos on the changed files (clean) and re-ran flutter test test/unit/email_repository_impl_test.dart (all 77 tests pass). Pushed as 29bc403 on issue-539-stable-imap-uid. PR: https://codeberg.org/guettli/sharedinbox/pulls/558

agentloop · agent: claude

Pushed the fix to the PR. CI was failing on `dart analyze --fatal-infos` (stricter than my local `flutter analyze`), specifically `require_trailing_commas` on a map literal in the new "no UIDPLUS" test. Pulled the literal into a `const` map with a trailing comma. Verified locally with `flutter analyze --fatal-infos` on the changed files (clean) and re-ran `flutter test test/unit/email_repository_impl_test.dart` (all 77 tests pass). Pushed as `29bc403` on `issue-539-stable-imap-uid`. PR: https://codeberg.org/guettli/sharedinbox/pulls/558 <sub>agentloop · agent: claude</sub>
guettlibot commented 2026-06-10 17:59:32 +00:00 (Migrated from codeberg.org)

Pushed the fix to the PR.

CI was failing on dart analyze --fatal-infos (stricter than my local flutter analyze), specifically require_trailing_commas on a map literal in the new "no UIDPLUS" test. Pulled the literal into a const map with a trailing comma. Verified locally with flutter analyze --fatal-infos on the changed files (clean) and re-ran flutter test test/unit/email_repository_impl_test.dart (all 77 tests pass). Pushed as 29bc403 on issue-539-stable-imap-uid. PR: https://codeberg.org/guettli/sharedinbox/pulls/558

agentloop · agent: claude

Pushed the fix to the PR. CI was failing on `dart analyze --fatal-infos` (stricter than my local `flutter analyze`), specifically `require_trailing_commas` on a map literal in the new "no UIDPLUS" test. Pulled the literal into a `const` map with a trailing comma. Verified locally with `flutter analyze --fatal-infos` on the changed files (clean) and re-ran `flutter test test/unit/email_repository_impl_test.dart` (all 77 tests pass). Pushed as `29bc403` on `issue-539-stable-imap-uid`. PR: https://codeberg.org/guettli/sharedinbox/pulls/558 <sub>agentloop · agent: claude</sub>
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: guettli/sharedinbox#539