Pre fetch email bodies #373
Closed
opened 2026-06-03 19:27:08 +00:00 by guettli
·
2 comments
No Branch/Tag Specified
main
issue-563-agentloop-validation
dummy-pr-test
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
NeedSupervisor
State/InProgress
State/Later
State/Planned
automerge
ci-failure
do-not-merge
loop/code
loop/code-ci-pending
loop/code-done
loop/code-in-process
loop/merge
loop/merge-done
loop/merge-in-process
loop/plan
loop/plan-done
loop/plan-in-process
Issue escalated to a human supervisor; agentloop will skip it until cleared.
Eligible for automatic merge by CI
Issue opened by agentloop to track a failing CI workflow; used for deduplication.
Plan PR — review only, do not merge.
Add to run the built-in "code" prompt; override at prompts/code.md.
Prompt "code" finished; waiting for the PR's CI to pass before advancing.
Prompt "code" finished successfully.
Agent for the "code" prompt is currently running on this issue.
Managed by agentloop
Managed by agentloop
Managed by agentloop
Add to run the built-in "plan" prompt; override at prompts/plan.md.
Prompt "plan" finished successfully.
Agent for the "plan" prompt is currently running on this issue.
No labels
NeedSupervisor
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: guettli/sharedinbox#373
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Email bodies should be available offline.
But the server could contain huge mailboxes with gigabyte of mails.
Create a plan how to keep mails in local cache without downloading all.
Aspects: large attachments don't need to be in local cache.
Check network: on mobile network download less.
User should be able to configure the local cache size.
Here is the implementation plan to post as a comment on issue #373:
Implementation Plan: Pre-fetch Email Bodies
Overview
Email bodies are currently fetched on-demand with a 7-day TTL. This plan adds a background pre-fetch mechanism that respects network type, skips large messages, and enforces a user-configured cache size limit. The existing
EmailBodies.cachedAtcolumn and background sync infrastructure inbackground_sync.dartare the natural hooks.Step 1 — Store message size during sync (schema v37)
Add
sizeBytes IntColumn(nullable) to theEmailstable inlib/data/db/database.dart. Populate it fromRFC822.SIZEduring the existing IMAP sync so we have a size signal without an extra round-trip at prefetch time.Run
dart run build_runner build --delete-conflicting-outputsafter the schema change.Step 2 — Add prefetch settings to UserPreferences
Extend
lib/core/models/user_preferences.dartwith two new fields:prefetchMode(enum:never/wifiOnly/always, defaultwifiOnly)bodyCacheLimitMb(int, default100)Persist them in the existing UserPreferences repository. No new DB table needed if prefs use a key-value store; otherwise add columns to whatever table backs them.
Step 3 — Add
connectivity_plusdependencyAdd
connectivity_plustopubspec.yaml. Createlib/core/services/network_info.dartwith a single function:Used by the prefetch service and background sync to gate downloads.
Step 4 — Body prefetch service
New file:
lib/core/services/body_prefetch_service.dartAlgorithm:
UserPreferences. IfprefetchMode == never, return immediately.prefetchMode == wifiOnlyand not on Wi-Fi, return immediately.SELECT SUM(length(textBody) + length(htmlBody)) FROM email_bodies. If already ≥bodyCacheLimitMb × 1024 × 1024, evict before prefetching (Step 5).Emailsrows that have no correspondingEmailBodiesrow (orcachedAtis null/expired), ordered byreceivedAt DESC, wheresizeBytes IS NULL OR sizeBytes < 512000(skip messages over ~500 KB to avoid large attachments dragging down performance).getEmailBody()logic — no new IMAP code needed, just reuse the existing on-demand fetch path.The 512 KB per-message threshold should be exposed as a constant (not user-configurable) for now.
Step 5 — Cache eviction
When cache size exceeds the limit, delete
EmailBodiesrows oldest-first (ORDER BY cachedAt ASC) until the size is back under the limit. Implement as a private helper in the prefetch service, called at the start of each prefetch run.Step 6 — Integrate with background sync
At the end of
_doBackgroundSync()inlib/core/sync/background_sync.dart, instantiate and callBodyPrefetchService.run(). The existingConstraints(networkType: NetworkType.connected)on the WorkManager task already gates this on connectivity; the prefetch service adds the WiFi-vs-mobile check on top.Step 7 — Settings UI
Add two controls to
lib/ui/screens/user_preferences_screen.dart:RadioGroup(Never / Wi-Fi only / Wi-Fi + mobile) — consistent with the existing radio groups for menu position.DropdownButtonor segmented control with options 50 / 100 / 200 / 500 MB, plus a read-only display of current cache size (computed from the DB query in Step 4).What is NOT downloaded during prefetch
BODY.PEEK[]) retrieves the full MIME message, but onlytextBodyandhtmlBodyare stored; binary parts are discarded. ThesizeBytesguard in Step 4 additionally skips entire messages that are too large to be worth fetching.File summary
lib/data/db/database.dartsizeBytestoEmails(schema v37)lib/core/db_schema_version.dartlib/core/models/user_preferences.dartprefetchMode,bodyCacheLimitMblib/data/repositories/user_preferences_repository_impl.dartpubspec.yamlconnectivity_pluslib/core/services/network_info.dartlib/core/services/body_prefetch_service.dartlib/core/sync/background_sync.dartlib/ui/screens/user_preferences_screen.dartagentloop: agent exceeded its 30m0s runtime budget and was stopped.
agent stderr tail: