0
0
Fork
You've already forked sharedinbox
0

Stable uid on IMAP #539

Open
opened 2026年06月08日 08:10:16 +02:00 by guettli · 5 comments

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 |
Collaborator
Copy link

PR opened: #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: #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>
Collaborator
Copy link

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).
Collaborator
Copy link

agentloop: agent hit the Anthropic quota; pausing this loop until 2026年06月10日T17: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月10日T17:40:00Z and will retry then. (Internal error: You're out of extra usage · resets 5:40pm (UTC) (account: tilldu@googlemail.com))
Collaborator
Copy link

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: #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>
Collaborator
Copy link

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: #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.
No Branch/Tag specified
main
issue-560-fix-firebase-run-url
issue-539-stable-imap-uid
issue-533-shared-email-list
plan-issue-555
drop-nix
plan-issue-484
plan-issue-539
plan-issue-535
plan-issue-474
plan-issue-533
fix-dagger-engineless-precommit
issue-521-fix-deploy-yml-wait-time-api
issue-502-fix-email-id-collision-mailbox
issue-492-eliminate-duplicate-build-runner
issue-494-website-change-detection
issue-491-parallelize-check
issue-478-fix-stalwart-dual-stack-bind
issue-475-allowed-addresses-glob
issue-473-search-result-reorder
issue-453-update-agentloop-defaults
issue-466-structured-search
issue-505-exclude-chaos-monkey-from-regular-ci
issue-509-fix-search-result-sorting
fix-ink-sparkle-remaining-tests
issue-506-fix-search-emails-tests
issue-504-runner-wait-time
issue-488-search-notes
issue-472-changelog-issue-links
issue-501-folder-search-local-sqlite
issue-486-fix-stale-test-shader-mismatch
fix/prevent-settled-search-rerun-473
issue-467-fix-search-stale-results
issue-446-installed-versions-in-changelog
issue-462-fix-pr
issue-448-chaos-monkey-test
issue-436-notes-on-emails
issue-429-unify-mail-display
issue-422-move-to-folder-create-new
issue-414-ensure-not-run-as-root
issue-424-unify-email-list-views
issue-419-trusted-senders-page
issue-425-fix-prs
test-foo
issue-421-bug-report
issue-383-fix-ci
issue-394-fix-deploy-flutter-version
issue-391-fix-ci-double-trigger
issue-376-combined-inbox-v2
issue-376-combined-inbox
issue-384-fix-open-prs
sops-migrate
issue-339-safe-first-on-imap-fetch
issue-340-try-catch-measure-height
issue-342-pin-intl-version
issue-341-guard-threademails-last
issue-335-agentloop-code-test
issue-329-fix
issue-315-fix
issue-320-fix
issue-325-fix
issue-312-fix
issue-311-fix
issue-305-fix
issue-304-fix
issue-299-fix
issue-300-fix
issue-298-fix
issue-296-fix
issue-294-fix
issue-289-fix
issue-288-fix
issue-287-fix
issue-286-fix
issue-277-fix
issue-282-fix
issue-280-fix
issue-272-fix
issue-268-fix
issue-267-fix
issue-266-fix
issue-258-fix
issue-260-fix
issue-257-fix
issue-253-fix
issue-216-fix
issue-251-fix
issue-249-fix
issue-question-fixes
issue-235-fix
issue-236-fix-v2
issue-237-fix
issue-236-fix
issue-228-fix
issue-217-fix
issue-214-fix
issue-213-fix
issue-208-fix
issue-205-fix
issue-204-fix
issue-203-fix
issue-202-fix
issue-129-fix
issue-161-fix
issue-160-fix
issue-201-fix
issue-210-fix
issue-198-fix
issue-200-fix
issue-144-fix
issue-199-fix
fix/playstore-upload-use-requests
issue-193-fix
issue-186-fix
issue-185-fix
issue-192-fix
issue-183-fix
issue-175-fix
issue-172-fix
issue-171-fix
issue-167-fix
issue-136-fix
issue-162-fix
issue-179-fix
issue-155-fix
issue-154-fix
issue-152-fix
issue-151-fix
issue-141-fix
issue-150-fix
issue-164-fix
migrate-to-dagger
task/d1-ci-matrix
task/a4-typeconverter-json
task/u7-onboarding-walkthrough
task/d3-sync-doc
task/a5-layer-boundary-lint
task/t5-golden-tests
task/p5-date-cache
task/s4-link-handling
task/p3-html-parse-isolate
task/u8-mark-all-read
task/u3-recent-searches
task/a3-jmap-injectable-http-client
task/r5-tls-error-handling
fix/playstore-redirect-retry
task/t3-repository-contract-tests
task/p2-email-list-pagination
task/p1-fts5-search
fix/playstore-upload-timeout
task/a1-email-detail-notifier
fix/upgrade-workmanager-0.9
fix/android-core-library-desugaring
task/p4-db-indexes
task/r3-html-error-boundary
task/d2-check-coverage
task/a2-email-tile
task/t4-migration-tests
task/t2-widget-tests
task/t1-email-repo-coverage
task/u6-connection-status
task/u4-push-notifications
task/u2-draft-sync
task/u1-list-unsubscribe
task/s2-hostname-validation
task/r6-reliability-fuzz-tests
task/r4-sync-error-banner
task/r2-force-resync
task/r1-undo-history-persistence
No results found.
Labels
Clear labels
automerge
Eligible for automatic merge by CI
ci-failure
Issue opened by agentloop to track a failing CI workflow; used for deduplication.
do-not-merge
Plan PR — review only, do not merge.
loop
code
Add to run the built-in "code" prompt; override at prompts/code.md.
loop/code-ci-pending
Prompt "code" finished; waiting for the PR's CI to pass before advancing.
loop
code-done
Prompt "code" finished successfully.
loop/code-in-process
Agent for the "code" prompt is currently running on this issue.
loop/merge
Managed by agentloop
loop/merge-done
Managed by agentloop
loop/merge-in-process
Managed by agentloop
loop
plan
Add to run the built-in "plan" prompt; override at prompts/plan.md.
loop
plan-done
Prompt "plan" finished successfully.
loop/plan-in-process
Agent for the "plan" prompt is currently running on this issue.
NeedSupervisor
Issue escalated to a human supervisor; agentloop will skip it until cleared.
State
InProgress
State
Later
State
Planned
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
guettli/sharedinbox#539
Reference in a new issue
guettli/sharedinbox
No description provided.
Delete branch "%!s()"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?