Skip to content

fix: remove a saved draft once its email is sent - #214

Open
LUCKYREDDY31 wants to merge 3 commits into
CopilotKit:mainfrom
LUCKYREDDY31:fix/remove-sent-draft
Open

LUCKYREDDY31 wants to merge 3 commits into
CopilotKit:mainfrom
LUCKYREDDY31:fix/remove-sent-draft

Conversation

@LUCKYREDDY31

Copy link
Copy Markdown
Contributor

Replaces #170, which GitHub closed when I accidentally deleted my fork. The review discussion and the before/after screenshots are on #170.

What changed

Sending a saved draft left it in Drafts, so the email looked unsent and was easy to send twice. Now the draft is removed once its email is sent.

  • Review email sends the draft's ID with the review. propose only accepts a draft that belongs to the same person, and links the review to it.
  • When the send succeeds, the draft is removed, but only if it still points to that review. A declined, expired, failed or uncertain send keeps the draft. If removing the draft fails, the error is logged and the send still reports success.
  • Saving a draft cancels its pending review, and a new review cancels the older one, so a draft never has two sends waiting.
  • Every save gives the draft a new revision, and so does linking a review. A review links only if the revision is unchanged since it started, so a save or a second review in the meantime cancels it with a 409 instead of letting it replace, and later delete, the newer draft. This is the race @jerelvelarde found on fix: remove a saved draft once its email is sent #170, plus the same race between two reviews started at once.
  • Edit details on a review that was replaced shows that outcome instead of opening an editor for a draft that no longer exists.

Verification

  • Tests cover each case from both reviews on fix: remove a saved draft once its email is sent #170. Each new test fails without its fix: the first five on e01080e, the revision tests on a816931, and the two-reviews test without the revision bump on link.
  • pnpm lint, pnpm typecheck, pnpm --dir apps/worker typecheck, pnpm test (494/494) and pnpm build:server pass on Node 24. On top of current main (a4944f4) the same checks pass at 531/531, along with pnpm test:browser, both Docker suites and the web, iOS and Android exports.
  • In the web app in sample mode: sending a draft removes it; rewriting after starting a review cancels that review and keeps the rewrite; two reviews of one draft leave the first cancelled and send one copy; an open review screen switches to cancelled when a newer review starts in another tab.

Integration limits

  • Tested in sample mode only, not with a real Gmail account.
  • Drafts left behind by sends before this change are not cleaned up.

Sending a saved draft left it in Drafts after the email went out. The
send request did not say which draft it came from, and nothing removed
drafts, so the person saw an email that looked unsent and could send it
twice.

The email.send proposal now carries an optional draftId, and the action
service removes that draft only after the send succeeds. A declined,
expired, failed or uncertain send keeps the draft. Edit details on the
review reopens the same draft instead of creating a copy.
A draft now records its pending review. Starting a new review or
saving the draft again cancels the older review, so one draft has at
most one pending send and older text cannot be sent over a rewrite.
A send removes the draft only if it still points to that review, and
a failed cleanup is logged instead of reported as a failed send.

propose rejects a draft that does not exist for the owner, and Edit
details opens the editor only when its own tap declined the review.
A save, or a second review, that landed while a review was still being
prepared could be replaced by the older review: propose linked the
review to whatever version of the draft existed when preparation
finished, so approving it sent the older text and deleted the newer
draft, and two reviews started together cancelled each other.

Every save now gives the draft a new revision, and linking a review
bumps it too. propose reads the revision before preparing and links the
review only if the draft still has it; otherwise the new review is
cancelled and the request returns 409. Drafts saved before this change
get a revision the first time they are reviewed.
@caspianchan31

Copy link
Copy Markdown

Read the full flow against main — the shape is right. Cleanup after a confirmed send, a guarded delete keyed on actionId (a draft re-saved after review no longer matches and survives), and a cleanup failure downgraded to backgroundFailure instead of reading as a failed send: all three are the correct calls. Two things it rests on that I checked: double-approve cannot double-send because decide already atomically claims awaiting_review → executing, and compareAndSwap is a jsonb shallow merge (data = data || patch), so linking a draft never clobbers its body.

One reachable edge this leaves open. On outcome_unknown the draft is intentionally kept — correct, since the email may already have gone out — but in the editor it still looks like an ordinary unsent draft; it only carries actionId. Someone landing back there from an "unknown result" review can reasonably assume nothing was sent and press Send again, producing a duplicate. Worth either surfacing a marker on a draft linked to an outcome_unknown action ("result unknown — check Sent in Gmail before resending") or gating the next send from that draft behind a confirm. It sits right next to this fix, so deciding it now saves a real double-send.

Minor: cleanup runs exactly once, after the terminal put. A transient DB error there leaves the draft linked to a succeeded action with no later trigger to retry. Fine if backgroundFailure is log-only; just confirming nothing is expected to reconcile it.

Not blocking — the core change is solid.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants