email_log.player_id is NULL on /admin/email/blast sends (missing FK write) #447
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
forgejo_admin/basketball-api#447
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Type
Bug
Lineage
Standalone — discovered 2026-04-10 during basketball-api#445 incident forensics. When querying email_log for the 3 wrong Apaisa emails, the
player_idcolumn was NULL on all 3 rows despite thequery_unsigned_contractsfunction returning player data.Repo
forgejo_admin/basketball-apiWhat Broke
The
/admin/email/blastendpoint writesemail_logrows without settingplayer_id, even when the query (e.g.query_unsigned_contracts) is player-scoped and returns a player per recipient.Verified in prod:
Rows 548, 549, 550 — all
contract_offer, all sent via/admin/email/blaston 2026-04-10 23:50 UTC — all haveplayer_id = NULL. By contrast, older rows from other send paths (send_contract_signed_email,send_jersey_reminder_email, announcements) correctly haveplayer_idpopulated.The
query_unsigned_contractsfunction returns dicts that includeparent_idand enough data to deriveplayer_id. The blast endpoint'ssend_templated_email()call passesparent_idto email_log but NOTplayer_id.Repro Steps
POST /admin/email/blastwithquery=unsigned_contractsand any layout/dataSELECT player_id FROM email_log WHERE id = (SELECT MAX(id) FROM email_log);Expected Behavior
When
query_unsigned_contracts(or any player-scoped query) is the recipient source, each email_log row should haveplayer_idset to the player whose contract triggered the email. This is:send_contract_signed_email, etc.) populate the columnEnvironment
admin.py's/admin/email/blasthandler, not the new endpoint)email_log(player_id column)Root Cause (likely)
query_unsigned_contractsreturns dicts withparent_id(for FK) but the recipient dict doesn't have an explicitplayer_idkey — player info is inplayer_name/team_namestrings only. The blast endpoint'ssend_templated_email()call passesparent_id=recipient.get("parent_id")without a correspondingplayer_id=....Expected Fix
player_idto the dict returned byquery_unsigned_contracts(and any other player-scoped query inemail_queries.py)/admin/email/blasthandler inroutes/admin.py, passplayer_id=recipient.get("player_id")tosend_templated_email()send_templated_email()already accepts aplayer_idkwarg — verify it writes to email_log correctlyAcceptance Criteria
query_unsigned_contractsreturns dicts withplayer_idkey/admin/email/blastpassesplayer_idtosend_templated_email()Related
westside-basketball— project this affectsplayer_idsfilter on blast (companion ticket, same file targets)