Skip to content

Leftovers from #1884: mailcore: a draft stays addressable by its local: id after reconcile_id #1892

Open

Description

@yeroo

Leftovers from #1884 (PR #1891): minor review findings and plan items deferred by the loop.

  • plan-1 (minor, plan out of scope, mailcore/src/sync/outbox.rs:33): mailcore: Move/MarkRead/SetFlag/SetCategories ops push a draft's local: id to Graph
  • plan-2 (minor, plan out of scope): mailcore: check calendar events for mailcore: a draft stays addressable by its local: id after reconcile_id #1884's stale local: id after reconcile_event_id
  • plan-3 (minor, plan out of scope, mailcore/src/sync/outbox.rs:52): mailcore: a retried SendDraft re-uploads its attachments
  • plan-4 (minor, plan out of scope): mailcore: a queued SaveDraft of a deleted never-synced draft quarantines instead of dropping
  • plan-5 (minor, plan out of scope, mailcore/src/store/mod.rs:536): mailcore: set busy_timeout on the store's connections
plan-1

SyncCommand::Move/MarkRead/SetFlag/SetCategories addressed with a draft's local: id still push that id to Graph (OutboxOp::Move { id: local } etc. → /me/messages/local:… → 4xx → quarantine → full reconverge). Since #1884 the store-side optimistic writes (move_message) resolve a reconciled local: id, but the queued ops do not. Pre-existing for any local: id. Resolve with Store::current_id in apply_op (as Delete does since #1884), and skip a still-local: id.

plan-2

Calendar events have the same local: → Graph id shape as drafts (Store::reconcile_event_id, OutboxOp::CreateEvent/UpdateEvent/DeleteEvent). An edit window left open across a CreateEvent drain may hold the local: id after reconcile, so a later UpdateEvent { local: } would not find the event (as #1884 was for drafts). Unconfirmed: check whether any caller keeps the id, and if so apply #1884's local_id resolve to events.

plan-3

OutboxOp::SendDraft uploads every outbound attachment, then sends (mailcore/src/sync/outbox.rs). If the send fails after the uploads (a 5xx/429/transport error), the op stays queued and the retry uploads every attachment again, so the delivered message carries duplicates. Pre-existing, for Graph ids too. Record uploaded attachments (or list the draft's attachments on Graph) and skip those already there.

plan-4

A SaveDraft { local: } (or SendDraft) still queued when its never-synced draft is deleted locally fails "no local draft stored for local:…" until it is quarantined after MAX_OP_ATTEMPTS, which clears every delta link and forces a full re-sync. A draft op whose local: id resolves to no row could be dropped as a no-op instead (as Delete of an unresolved local: id is since #1884).

plan-5

The mail store is opened on two connections (the UI's and the sync thread's) and neither sets busy_timeout, so a write that meets the other connection's write lock fails at once with SQLITE_BUSY (Store::init in mailcore/src/store/mod.rs). Set a short busy_timeout in init.

Source: #1884, PR #1891
Severity: minor

Activity

  1. added
    follow-upfiled automatically by an agworkbench loop
    priority:P2correctness defect that blocks nothing open
    on Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    follow-upfiled automatically by an agworkbench looppriority:P2correctness defect that blocks nothing open

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions