fix: mark decision emails sent per recipient after delivery, not before dispatch - #4
Merged
Conversation
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes audit finding M2:
POST /superadmin/emails/decisionsstampeddecision_email_sent_at/announcement_email_sent_atfor the whole batch before handing the send to a detached goroutine, then un-marked failures at the end. If the process died mid-run (Cloud Run instance reaped, deploy, OOM) every unsent recipient stayed marked as emailed, was excluded from every later "unsent only" run, and showed as sent in the stats — recoverable only viaresend_all, which double-sends the ones that did go out.Ordering is now:
dispatchDecisionEmailsmarks each recipient individually, right after its send succeeds (10s bounded ctx). Failed sends are simply never marked, so the "clear marker on failure" pass is gone. A crash now leaves the unsent remainder unmarked → retried next run. The only remaining bad window is sent but killed before the 1-row UPDATE → at worst one duplicate email, which is the safe direction.application.decisionEmailInFlight(atomic.Bool): a second request while a run is sending gets 409decision emails are already being sent. The flag is taken before the recipient query (and released on every early return) so a second request can't snapshot recipients the current run is still working through. Guard is per-process; with >1 Cloud Run instance two admins clicking within the same run could still overlap, same as before.application.backgroundJobs(sync.WaitGroup) tracks the dispatch goroutine, andrun()now drains it afterserver.Shutdownwithin the existing 5s shutdown ctx (drainBackgroundJobs), so a SIGTERM lets in-flight sends finish writing their markers.SendEmailsDialogalready routes non-200 througherrorAlert. Side effect worth knowing: the stats it refreshes immediately after the 200 now lag until sends actually complete (previously they jumped to "sent" instantly).task gen-docs) for the 409 + description.Tests updated for the new contract: per-recipient marks only after a successful send, failed recipients untouched, marker-write failure doesn't stop the batch, 409 on overlapping runs (first send held open via a channel so the overlap is real), and the lock is released on early-return paths.
go build/vet,gofmt,go test -race ./...pass.Link to Devin session: https://app.devin.ai/sessions/8d5427d328ee4f57acf27d50df7f2af8
Open in Devin Desktop: https://app.devin.ai/desktop/session/8d5427d328ee4f57acf27d50df7f2af8?variant=devin
Requested by: @balebbae