recurring-reports-post-offboarding
Recurring Reports Firing After Trial Expiry or Workspace Offboarding
This skill diagnoses the class of incidents where users continue receiving report emails after their trial has expired or their workspace has been churned — and provides the mitigation to stop them immediately.
What This Skill Debugs
Churned or expired workspaces can leak emails through two independent email systems. Both must be checked.
System 1 — RecurringReportSchedule → SCHEDULED_GENERATED_CONTRACT_SEARCH_REPORT
RecurringReportSchedule rows are not deleted or deactivated when:
- A trial workspace expires —
UpdateExpiredTrialSignupsUseCaseandPurgeTrialDataUseCase(pre-fix) did not clean upRecurringReportSchedulerows. - A workspace is churned/offboarded without the
delete_workspacemanagement command — the soft-delete chain inSoftDeletionModeldoes not fire Django's DB-levelCASCADE, leavingRecurringReportSchedulerows alive.
The daily cron (send_due_today_recurring_report_cron_task) has no trial-status or workspace-active guard — it picks up all RecurringReportSchedule rows where next_send_at falls today and is_deleted=False, regardless of workspace state.
System 2 — EmailConfig + EXPIRING_CONTRACTS_EMAIL_REPORT_ENABLED FF → CONTRACT_EXPIRATION_REPORT
The weekly contract expiry reminder system is independent of RecurringReportSchedule. It fires based on emails_emailconfig rows and the workspace-level feature flag EXPIRING_CONTRACTS_EMAIL_REPORT_ENABLED. When a workspace is churned without running delete_workspace, EmailConfig rows survive and the feature flag remains enabled — so expiry reminder emails keep firing weekly even after all RecurringReportSchedule rows have been soft-deleted.