OUT-3915 | Delete entries on GroupedEmailEvents table after emails are successfully sent - #1351
Conversation
…flush (OUT-3915) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…ail-events-after-flush
|
Deployment failed with the following error: Learn More: https://vercel.link/multiple-function-regions |
| const deleteWindowRows = (db: ReturnType<typeof DBClient.getInstance>, windowKey: string) => | ||
| db.$executeRaw`DELETE FROM "GroupedEmailEvents" WHERE "windowKey" = ${windowKey}` |
There was a problem hiding this comment.
Broad DELETE may silently drop unread rows
deleteWindowRows deletes every row matching windowKey, regardless of sentAt. The flush reads rows once at startup (readUnsentWindowEvents), then spends time in the send loop. Any GroupedEmailEvents row inserted for this same windowKey after that initial read — but before the DELETE — is silently destroyed without being sent.
This is a latent risk if the upstream event-creation path can assign new events to an already-flushing window (e.g., a delayed webhook or a retry that reassigns the same windowKey within the send-loop window). Adding a sentAt IS NOT NULL guard to deleteWindowRows would make the cleanup strictly scoped to rows that were successfully processed and avoid touching anything that arrived late.
There was a problem hiding this comment.
Good catch. Fixed by scoping the DELETE to sentAt IS NOT NULL — only rows that were successfully processed by the loop get cleaned up. Any row that arrives after readUnsentWindowEvents but before the delete will have sentAt IS NULL and be preserved.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@greptile can you re review. |
… adding deleteWindowRows Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
DELETE FROM GroupedEmailEvents WHERE windowKey = $windowKeyat the end offlushGroupedEmailRun, executed only on full successsentAtso they'll never be re-sent)markRecipientSentis preserved — still needed for partial-failure retry safety mid-loopWhy
GroupedEmailEventsrows serve as a 5-minute buffer and are not needed after the window is flushed. Previously they accumulated indefinitely withsentAtset, bulking up the table with data that was never read again.Behaviour
deleteWindowRowsis reached — rows stay intact for Trigger.dev retrysentAtset (harmless, ignored by future queries)Test plan
totalCount === 0(full row deletion) after successful runs rather than checking unsent counttotalCount === 2) and are deleted after the successful retry (totalCount === 0)🤖 Generated with Claude Code