# Profile Auto-Delete System Reference This document provides comprehensive documentation on the profile auto-delete system, including configuration, commands, workflows, and testing procedures. ## Table of Contents 1. [Overview](#overview) 2. [System Architecture](#system-architecture) 3. [Configuration Structure](#configuration-structure) 4. [Artisan Commands](#artisan-commands) 5. [Workflow Process](#workflow-process) 6. [Scheduled Execution](#scheduled-execution) 7. [Testing](#testing) 8. [Production Configuration](#production-configuration) 9. [Troubleshooting](#troubleshooting) 10. [Related Systems](#related-systems) --- ## Overview The profile auto-delete system automatically manages inactive profiles through a two-step process: 1. **Mark Inactive**: Profiles that haven't logged in for a configured period are automatically marked as inactive 2. **Process Inactive**: Inactive profiles receive progressive warning emails before final deletion ### Purpose - Maintain platform health by removing abandoned profiles - Protect user privacy by deleting old personal data - Send warning emails giving users a chance to prevent deletion - Comply with data retention policies and GDPR requirements - Keep the active user base current and engaged ### Key Features - **Two-step process**: Separate inactive marking from deletion - **Progressive warnings**: 3 escalating warning emails before deletion - **Grace period**: Deleted profiles can be restored within configurable timeframe (default: 30 days) - **Configurable thresholds**: All timeframes configurable per platform - **Balance protection**: Prevents deletion of profiles with negative balances - **Transaction anonymization**: Past transactions preserved but anonymized - **Balance handling**: Multiple options for account balance disposal - **Automatic scheduling**: Runs daily via Laravel scheduler - **Comprehensive logging**: All actions logged for audit trail --- ## System Architecture ### Two-Step Process #### Step 1: Mark Inactive (Daily at 01:30) ``` ┌─────────────────────────────────────────────────────────────┐ │ profiles:mark-inactive │ ├─────────────────────────────────────────────────────────────┤ │ • Finds profiles where last_login_at > days_not_logged_in │ │ • Sets inactive_at = now() │ │ • Profile becomes hidden/labeled (per config) │ │ • Logs action to mark-inactive-profiles.log │ └─────────────────────────────────────────────────────────────┘ ``` #### Step 2: Process Inactive (Daily at 02:00) ``` ┌─────────────────────────────────────────────────────────────┐ │ profiles:process-inactive │ ├─────────────────────────────────────────────────────────────┤ │ • Finds profiles where inactive_at is set │ │ • Calculates days since inactive_at │ │ • Sends appropriate warning based on thresholds: │ │ - Warning 1: X days after inactive │ │ - Warning 2: Y days after inactive │ │ - Final Warning: Z days after inactive │ │ - Delete: N days after inactive │ │ • Logs all actions to inactive-profiles.log │ └─────────────────────────────────────────────────────────────┘ ``` ### Database Schema **Users/Organizations Table:** - `last_login_at` (timestamp, nullable) - Last successful login - `inactive_at` (timestamp, nullable) - When profile was marked inactive - `deleted_at` (timestamp, nullable) - Soft delete timestamp **Field States:** - `inactive_at = NULL` → Profile is active - `inactive_at = [past date]` → Profile is inactive, awaiting deletion - `deleted_at = [date]` → Profile is soft-deleted --- ## Configuration Structure ### Location Configuration is defined in: - `config/timebank-default.php` (lines 1405-1420+) - `config/timebank_cc.php` (lines 1405-1420+) - Active config loaded via `TIMEBANK_CONFIG` environment variable ### Complete Configuration ```php // Step 1: Mark profiles as inactive 'profile_inactive' => [ 'days_not_logged_in' => 350, // Profile marked inactive after this many days 're-activate_at_login' => true, // Auto-reactivate when user logs in 'messenger_hidden' => true, // Hide from messenger search 'profile_search_hidden' => true, // Hide from main search 'profile_hidden' => true, // Hide profile page from non-admins 'profile_labeled' => true, // Show "Inactive" label ], // Step 2: Process inactive profiles (send warnings and delete) 'delete_profile' => [ // NOTE: These are days AFTER the profile is marked inactive // Total time = days_not_logged_in + days_after_inactive 'days_after_inactive' => [ 'warning_1' => 7, // First warning 'warning_2' => 10, // Second warning 'warning_final' => 14, // Final warning 'run_delete' => 15, // Delete profile ], 'account_balances' => [ // Handle account balances on deletion (elsif logic): 'donate_balances_to_organization_account_specified' => true, 'transfer_balances_to_bank_client' => false, 'transfer_balances_to_account_id' => null, 'transfer_balances_to_debit_account' => true, ], ], ``` ### Configuration Timeline Example With the configuration above: - **Day 0**: User last logs in - **Day 350**: Profile marked inactive (`profiles:mark-inactive`) - **Day 357**: First warning email sent (350 + 7) - **Day 360**: Second warning email sent (350 + 10) - **Day 364**: Final warning email sent (350 + 14) - **Day 365**: Profile deleted (350 + 15) **Total: 1 year from last login to deletion** --- ## Artisan Commands ### 1. Mark Inactive Profiles **Command:** `php artisan profiles:mark-inactive` **Purpose:** Automatically mark profiles as inactive based on login inactivity. **What It Does:** 1. Finds all User and Organization profiles where: - `last_login_at` is older than configured `days_not_logged_in` - `inactive_at` is NULL (not already marked inactive) 2. Sets `inactive_at` to current timestamp 3. Logs action to `storage/logs/mark-inactive-profiles.log` **Usage:** ```bash # Mark all eligible profiles as inactive php artisan profiles:mark-inactive ``` **Output Example:** ``` Checking profiles for inactivity... [User] Marked inactive: John Doe (1095 days) [User] Marked inactive: Jane Smith (1200 days) [Organization] Marked inactive: Community Center (800 days) Processing complete: 3 profiles marked as inactive ``` **Log File:** `storage/logs/mark-inactive-profiles.log` ``` [2025-11-14 01:30:15] === Starting profile inactivity check === [2025-11-14 01:30:16] [User] Marked INACTIVE: John Doe (ID: 123) - Not logged in for 1095 days (last login: 2022-01-01) [2025-11-14 01:30:17] [User] Marked INACTIVE: Jane Smith (ID: 456) - Not logged in for 1200 days (last login: 2021-09-15) [2025-11-14 01:30:18] === Completed: 3 profiles marked inactive === ``` **Schedule:** Runs daily at 01:30 (via `app/Console/Kernel.php`) --- ### 2. Process Inactive Profiles **Command:** `php artisan profiles:process-inactive` **Purpose:** Send warning emails and delete profiles based on inactivity duration. **What It Does:** 1. Finds all User and Organization profiles where: - `inactive_at` is set (marked as inactive) - `deleted_at` is NULL (not already deleted) 2. Calculates days since `inactive_at` 3. Determines action based on thresholds: - Send Warning 1 email - Send Warning 2 email - Send Final Warning email - Delete profile 4. Emails are queued (not sent immediately) 5. Logs all actions to `storage/logs/inactive-profiles.log` **Usage:** ```bash # Process all inactive profiles php artisan profiles:process-inactive ``` **Output Example:** ``` Processing inactive profiles... [User] warning_1: John Doe (7 days remaining) [User] warning_2: Jane Smith (5 days remaining) [Organization] final: Community Center (1 day remaining) [User] Deleted: Old User Processing complete: 3 warnings sent, 1 profiles deleted ``` **Log File:** `storage/logs/inactive-profiles.log` ``` [2025-11-14 02:00:15] === Starting inactive profile processing === [2025-11-14 02:00:16] [User] warning_1 sent to John Doe (ID: 123) - Inactive for 7.5 days, 7.5 days remaining [2025-11-14 02:00:17] [User] warning_2 sent to Jane Smith (ID: 456) - Inactive for 10.2 days, 4.8 days remaining [2025-11-14 02:00:18] [Organization] final sent to Community Center (ID: 789) - Inactive for 14.1 days, 0.9 days remaining [2025-11-14 02:00:19] [User] DELETED Old User (ID: 321) - Inactive for 15.5 days - Confirmation email sent to olduser@example.com [2025-11-14 02:00:20] === Completed: 3 warnings, 1 deletions === ``` **Schedule:** Runs daily at 02:00 (via `app/Console/Kernel.php`) **Important Notes:** - Emails are queued, not sent immediately (requires queue worker) - Profiles with negative balances are skipped and logged - Each profile receives only ONE email per day (based on current threshold) - Soft-deleted profiles are automatically excluded - Deletion confirmation emails are sent automatically after successful deletion --- ## Workflow Process ### Full Lifecycle Example #### Timeline: Production Configuration ``` Day 0 (Jan 1, 2024): ├─ User logs in for last time └─ last_login_at = 2024-01-01 10:00:00 Day 350 (Dec 16, 2024) at 01:30: ├─ profiles:mark-inactive runs ├─ Finds user (350 days since last login) ├─ Sets inactive_at = 2024-12-16 01:30:00 ├─ Profile becomes hidden/labeled └─ Log: "Marked INACTIVE: User (350 days)" Day 350 (Dec 16, 2024) at 02:00: ├─ profiles:process-inactive runs ├─ User inactive for 0 days (just marked) └─ No action (threshold not reached) Day 357 (Dec 23, 2024) at 02:00: ├─ profiles:process-inactive runs ├─ User inactive for 7 days ├─ Threshold: warning_1 (7 days) ├─ Queues Warning 1 email └─ Log: "warning_1 sent, 8 days remaining" Email Content (Warning 1): ├─ Subject: "Warning: Your profile will be deleted soon" ├─ Header: "Inactive profile warning" ├─ Message: "Your profile has been inactive for 357 days..." ├─ Shows: Account balances, time remaining └─ Button: "Activate profile" Day 360 (Dec 26, 2024) at 02:00: ├─ profiles:process-inactive runs ├─ User inactive for 10 days ├─ Threshold: warning_2 (10 days) ├─ Queues Warning 2 email └─ Log: "warning_2 sent, 5 days remaining" Day 364 (Dec 30, 2024) at 02:00: ├─ profiles:process-inactive runs ├─ User inactive for 14 days ├─ Threshold: warning_final (14 days) ├─ Queues Final Warning email └─ Log: "final sent, 1 day remaining" Day 365 (Dec 31, 2024) at 02:00: ├─ profiles:process-inactive runs ├─ User inactive for 15 days ├─ Threshold: run_delete (15 days) ├─ Checks for negative balances → None ├─ Executes DeleteUser action (soft delete) ├─ Sets deleted_at = 2024-12-31 02:00:00 ├─ Balance handling preferences stored in comment field ├─ Profile enters grace period (30 days by default) ├─ User can restore profile during grace period ├─ Deletion confirmation email sent └─ Log: "DELETED User (365 days total)" Day 395 (Jan 30, 2025) at 02:30: ├─ profiles:permanently-delete-expired runs ├─ Grace period expired (30 days since deletion) ├─ Executes permanent deletion ├─ Personal data anonymized ├─ Transactions anonymized ├─ Account balances handled per stored preferences └─ Profile cannot be restored after this point ``` ### User Reactivation Scenarios #### Scenario 1: User Logs In During Warning Period ``` Day 357: Warning 1 sent Day 358: User logs in ├─ Auto-reactivation enabled (re-activate_at_login = true) ├─ Sets inactive_at = NULL ├─ Profile becomes active again ├─ User sees "Welcome back!" message └─ Process stops (profile no longer inactive) ``` #### Scenario 2: User Clicks "Activate Profile" Button ``` Day 359: User clicks button in warning email ├─ Redirected to login page with intended route ├─ User logs in successfully ├─ Auto-reactivation triggers ├─ Sets inactive_at = NULL └─ Profile active, deletion prevented ``` #### Scenario 3: User Does Nothing ``` Day 357: Warning 1 sent → User ignores Day 360: Warning 2 sent → User ignores Day 364: Final Warning → User ignores Day 365: Profile soft-deleted → Grace period begins (30 days) Day 395: Grace period expires → Profile permanently deleted ``` #### Scenario 4: Profile Restoration During Grace Period ``` Day 365: Profile soft-deleted (automatic deletion after inactivity) ├─ Deletion confirmation email sent ├─ Profile enters 30-day grace period └─ Profile can be restored by admin Day 370: Admin restores profile (5 days after deletion) ├─ Command: php artisan profiles:restore username ├─ Sets deleted_at = NULL ├─ Clears stored balance handling preferences ├─ Profile becomes active again └─ User can log in normally ``` --- ## Scheduled Execution ### Scheduler Configuration **Location:** `app/Console/Kernel.php` (lines 51-63) ```php // Mark profiles as inactive when they haven't logged in for configured days $schedule->command('profiles:mark-inactive') ->daily() ->at('01:30') ->withoutOverlapping() ->appendOutputTo(storage_path('logs/mark-inactive-profiles.log')); // Process inactive profiles daily (send warnings and soft-delete profiles) $schedule->command('profiles:process-inactive') ->daily() ->at('02:00') ->withoutOverlapping() ->appendOutputTo(storage_path('logs/inactive-profiles.log')); // Permanently delete profiles after grace period expires $schedule->command('profiles:permanently-delete-expired') ->daily() ->at('02:30') ->withoutOverlapping() ->appendOutputTo(storage_path('logs/permanent-deletions.log')); ``` ### Execution Times - **01:30**: Mark inactive profiles (Step 1) - **02:00**: Process inactive profiles (Step 2 - soft delete) - **02:30**: Permanently delete expired profiles (after grace period) 30-minute gap ensures profiles marked at 01:30 won't be immediately processed. ### Enabling the Scheduler **Crontab Entry:** ```bash * * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1 ``` **Verify Schedule:** ```bash # List all scheduled tasks php artisan schedule:list # Test scheduler without waiting php artisan schedule:test ``` --- ## Testing ### Testing Configuration For rapid testing, use minute-based values: ```php 'profile_inactive' => [ 'days_not_logged_in' => 0.000694, // ~1 minute ], 'delete_profile' => [ 'days_after_inactive' => [ 'warning_1' => 0.001389, // ~2 minutes after marked inactive 'warning_2' => 0.00208, // ~3 minutes after marked inactive 'warning_final' => 0.00278, // ~4 minutes after marked inactive 'run_delete' => 0.00347, // ~5 minutes after marked inactive ], ], ``` **Conversion Formula:** `days = minutes / 1440` ### Test Script: All 3 Warnings **File:** `test-all-warnings.sh` ```bash #!/bin/bash USER_ID=${1:-102} echo "=== Testing All 3 Warning Emails for User ID: $USER_ID ===" # Warning 1: Set inactive_at to 2 minutes ago echo "=== Test 1: Warning 1 (inactive for 2 minutes) ===" php artisan tinker --execute=" \$user = App\Models\User::find($USER_ID); \$user->inactive_at = now()->subMinutes(2); \$user->save(); exit; " php artisan profiles:process-inactive sleep 2 # Warning 2: Set inactive_at to 3 minutes ago echo "=== Test 2: Warning 2 (inactive for 3 minutes) ===" php artisan tinker --execute=" \$user = App\Models\User::find($USER_ID); \$user->inactive_at = now()->subMinutes(3); \$user->save(); exit; " php artisan profiles:process-inactive sleep 2 # Warning Final: Set inactive_at to 4 minutes ago echo "=== Test 3: Warning Final (inactive for 4 minutes) ===" php artisan tinker --execute=" \$user = App\Models\User::find($USER_ID); \$user->inactive_at = now()->subMinutes(4); \$user->save(); exit; " php artisan profiles:process-inactive echo "=== All 3 warning emails sent! ===" php artisan queue:work --stop-when-empty --timeout=30 ``` **Usage:** ```bash chmod +x test-all-warnings.sh ./test-all-warnings.sh 102 ``` ### Manual Testing Steps #### Test 1: Mark Inactive ```bash # Setup test user php artisan tinker --execute=" \$user = App\Models\User::find(102); \$user->last_login_at = now()->subMinutes(2); \$user->inactive_at = null; \$user->save(); echo 'User setup: last login 2 minutes ago'; exit; " # Run mark-inactive command php artisan profiles:mark-inactive # Verify user is now inactive php artisan tinker --execute=" \$user = App\Models\User::find(102); echo 'inactive_at: ' . \$user->inactive_at . PHP_EOL; echo 'Is active: ' . (\$user->isActive() ? 'Yes' : 'No'); exit; " ``` #### Test 2: Warning Emails ```bash # Setup user for Warning 1 php artisan tinker --execute=" \$user = App\Models\User::find(102); \$user->inactive_at = now()->subMinutes(2); \$user->save(); exit; " # Process inactive profiles php artisan profiles:process-inactive # Process email queue php artisan queue:work --stop-when-empty # Check Mailpit for email ``` #### Test 3: Deletion ```bash # Setup user for deletion php artisan tinker --execute=" \$user = App\Models\User::find(102); \$user->inactive_at = now()->subMinutes(6); \$user->save(); exit; " # Process inactive profiles (will delete) php artisan profiles:process-inactive # Verify deletion php artisan tinker --execute=" \$user = App\Models\User::withTrashed()->find(102); echo 'deleted_at: ' . \$user->deleted_at . PHP_EOL; exit; " ``` ### Verification Checklist - [ ] Command logs to correct file - [ ] Profiles marked inactive at right threshold - [ ] Warning 1 email received - [ ] Warning 2 email received - [ ] Final warning email received - [ ] Profile deleted at correct threshold - [ ] Profile with negative balance skipped - [ ] Already deleted profiles not reprocessed - [ ] Email queue processed successfully - [ ] Transactions anonymized after deletion --- ## Production Configuration ### Recommended Production Settings ```php 'profile_inactive' => [ 'days_not_logged_in' => 350, // ~11.5 months 're-activate_at_login' => true, 'messenger_hidden' => true, 'profile_search_hidden' => true, 'profile_hidden' => true, 'profile_labeled' => true, ], 'delete_profile' => [ 'days_after_inactive' => [ 'warning_1' => 7, // 357 days total (~11.75 months) 'warning_2' => 10, // 360 days total (~11.83 months) 'warning_final' => 14, // 364 days total (~12 months) 'run_delete' => 15, // 365 days total (1 year) ], ], ``` **Total Timeline:** 1 year from last login to deletion ### Alternative: Strict 3-Year Retention ```php 'profile_inactive' => [ 'days_not_logged_in' => 1080, // ~3 years ], 'delete_profile' => [ 'days_after_inactive' => [ 'warning_1' => 7, // 1087 days total 'warning_2' => 10, // 1090 days total 'warning_final' => 14, // 1094 days total 'run_delete' => 15, // 1095 days total (3 years) ], ], ``` ### Balance Handling Options **Option 1: Donate to Organization (Recommended)** ```php 'account_balances' => [ 'donate_balances_to_organization_account_specified' => true, 'transfer_balances_to_bank_client' => false, 'transfer_balances_to_account_id' => null, 'transfer_balances_to_debit_account' => true, ], ``` Users can select an organization to receive their balance during deletion flow. **Option 2: Remove Currency (Debit Account)** ```php 'account_balances' => [ 'donate_balances_to_organization_account_specified' => false, 'transfer_balances_to_bank_client' => false, 'transfer_balances_to_account_id' => null, 'transfer_balances_to_debit_account' => true, // Removes from circulation ], ``` Balance transferred to debit account (removes dead currency). **Option 3: Transfer to Specific Account** ```php 'account_balances' => [ 'donate_balances_to_organization_account_specified' => false, 'transfer_balances_to_bank_client' => false, 'transfer_balances_to_account_id' => 123, // Specific account ID 'transfer_balances_to_debit_account' => false, ], ``` All balances go to a designated account (e.g., community fund). --- ## Troubleshooting ### Issue: Commands Not Running Automatically **Symptoms:** - Profiles not being marked inactive - No warning emails sent - Logs not updating **Diagnosis:** ```bash # Check if scheduler is configured crontab -l | grep artisan # Verify schedule:run works php artisan schedule:run # List scheduled tasks php artisan schedule:list ``` **Solution:** ```bash # Add to crontab crontab -e # Add this line: * * * * * cd /path-to-project && php artisan schedule:run >> /dev/null 2>&1 # Verify php artisan schedule:test ``` --- ### Issue: Emails Not Sending **Symptoms:** - Log shows "warning sent" but no email received - Queue contains jobs but they're not processing **Diagnosis:** ```bash # Check queue status php artisan queue:failed # Check mail configuration php artisan tinker --execute="dd(config('mail'));" # Verify queue worker is running ps aux | grep "queue:work" ``` **Solution:** ```bash # Start queue worker php artisan queue:work # Or use supervisor for persistent queue worker # See: Laravel Queue documentation ``` --- ### Issue: Already Deleted Profiles Being Processed **Symptoms:** - Log shows deletions for profiles that were already deleted - "DELETED" entries for same profile multiple times **Fixed In:** Version with `whereNull('deleted_at')` filter (app/Console/Commands/ProcessInactiveProfiles.php:50, 61) **Verification:** ```bash # Check ProcessInactiveProfiles.php contains: grep "whereNull('deleted_at')" app/Console/Commands/ProcessInactiveProfiles.php # Should see: # ->whereNull('deleted_at') // Exclude already deleted profiles ``` --- ### Issue: Profiles with Negative Balances Not Skipped **Symptoms:** - Error when trying to delete profile with negative balance - Deletion process crashes **Expected Behavior:** - Profiles with negative balances should be skipped - Log entry: "SKIPPED deletion - Has negative balance" **Verification:** ```bash # Check logs for skip messages grep "SKIPPED" storage/logs/inactive-profiles.log # Manually test php artisan tinker --execute=" \$user = App\Models\User::find(102); \$accounts = \$user->accounts()->active()->notRemoved()->get(); foreach (\$accounts as \$account) { echo \$account->name . ': ' . \$account->balance . PHP_EOL; } exit; " ``` --- ### Issue: Wrong Warning Level Sent **Symptoms:** - User receives Warning 2 but should get Warning 1 - Only one warning email instead of three **Understanding:** The system sends ONE warning per day based on CURRENT inactive duration: - If user is 7 days inactive → Warning 1 - If user is 10 days inactive → Warning 2 - If user is 14 days inactive → Final Warning This is by design. Users naturally progress through warnings as time passes. **Not a Bug:** Each profile gets the appropriate warning for their current state, not all warnings at once. --- ### Issue: Configuration Not Taking Effect **Symptoms:** - Changed config values but behavior unchanged - Still using old thresholds **Solution:** ```bash # Clear config cache php artisan config:clear # Rebuild cache php artisan config:cache # Verify config loaded php artisan tinker --execute=" echo 'days_not_logged_in: ' . timebank_config('profile_inactive.days_not_logged_in') . PHP_EOL; echo 'warning_1: ' . timebank_config('delete_profile.days_after_inactive.warning_1') . PHP_EOL; exit; " ``` --- ## Related Systems ### 1. Profile Inactive System **Documentation:** [PROFILE_INACTIVE_CONFIG.md](PROFILE_INACTIVE_CONFIG.md) The auto-delete system builds on the profile inactive system: - Uses same `inactive_at` field - Respects same visibility/hiding rules - Triggered by same `isActive()` method **Integration:** - `MarkInactiveProfiles` sets `inactive_at` - Profile becomes hidden (per inactive config) - `ProcessInactiveProfiles` processes these inactive profiles - Progressive warnings sent before deletion ### 2. User Deletion Action **Location:** `app/Actions/Jetstream/DeleteUser.php` The auto-delete system uses the existing `DeleteUser` action: ```php $deleteUser = new DeleteUser(); $deleteUser->delete($profile, 'delete', null); ``` **What It Does:** - Anonymizes profile data - Preserves transactions (anonymized) - Handles account balances (per config) - Soft deletes profile - **Note:** Does NOT send deletion email (handled by ProcessInactiveProfiles command) ### 3. Email System #### Warning Emails **Mailable Classes:** - `app/Mail/InactiveProfileWarning1Mail.php` - `app/Mail/InactiveProfileWarning2Mail.php` - `app/Mail/InactiveProfileWarningFinalMail.php` **Email Templates:** - `resources/views/emails/inactive-profiles/{locale}/warning-1.blade.php` - `resources/views/emails/inactive-profiles/{locale}/warning-2.blade.php` - `resources/views/emails/inactive-profiles/{locale}/warning-final.blade.php` **Languages Supported:** en, nl, de, es, fr #### Deletion Confirmation Email **Mailable Class:** - `app/Mail/UserDeletedMail.php` **Email Template:** - `resources/views/emails/administration/{locale}/user-deleted.blade.php` **Sent by:** `ProcessInactiveProfiles` command (app/Console/Commands/ProcessInactiveProfiles.php:194) **When:** Immediately after successful profile deletion **Content:** - Account details (username, full name, email) - Deletion timestamp - Balance handling information (total balance, donation details if applicable) - Confirmation that data has been permanently deleted and anonymized - Note that action is irreversible **Variables:** ```php [ 'time' => '18 November 2025, 22:26', 'deletedUser' => (object)[ 'name' => 'ProfileName', 'full_name' => 'Full Profile Name', 'lang_preference' => 'en', ], 'mail' => 'user@example.com', 'balanceHandlingOption' => 'delete', 'totalBalance' => 120, // in minutes 'donationAccountId' => null, 'donationAccountName' => null, 'donationOrganizationName' => null, ] ``` **Languages Supported:** en, nl, de, es, fr ### 4. Queue System All emails (warnings and deletion confirmations) are queued for asynchronous delivery: - Uses Laravel queue system - Requires queue worker running - Prevents command timeout for large batches - Emails are sent in the user's preferred language (`lang_preference`) **Queue Configuration:** `config/queue.php` **Email Types Queued:** 1. Warning emails (InactiveProfileWarning1Mail, InactiveProfileWarning2Mail, InactiveProfileWarningFinalMail) 2. Deletion confirmation emails (UserDeletedMail) ### 5. Profile Deletion Grace Period **Documentation:** [PROFILE_DELETION_GRACE_PERIOD.md](PROFILE_DELETION_GRACE_PERIOD.md) When profiles are deleted (either automatically or manually), they enter a grace period before permanent deletion: **How It Works:** - Profile is soft-deleted (`deleted_at` timestamp set) - Balance handling preferences stored in `comment` field as JSON - Profile remains in grace period for configurable days (default: 30) - Profile can be restored during grace period using `php artisan profiles:restore` - After grace period expires, `profiles:permanently-delete-expired` command: - Anonymizes profile data permanently - Handles account balances per stored preferences - Makes restoration impossible **Configuration:** ```php 'delete_profile' => [ 'grace_period_days' => 30, // Days before permanent deletion ], ``` **Restoration Command:** ```bash # List all restorable profiles php artisan profiles:restore --list # Restore specific profile php artisan profiles:restore username ``` **Integration with Auto-Delete:** - Auto-deleted profiles enter grace period automatically - Deletion confirmation email mentions grace period - Admin can restore accidentally deleted profiles - Permanent deletion happens 30 days after initial deletion --- ## Monitoring ### Log Files **Mark Inactive Log:** `storage/logs/mark-inactive-profiles.log` ```bash # View recent marks tail -n 50 storage/logs/mark-inactive-profiles.log # Count marked today grep "$(date +%Y-%m-%d)" storage/logs/mark-inactive-profiles.log | wc -l ``` **Process Inactive Log:** `storage/logs/inactive-profiles.log` ```bash # View recent activity tail -n 100 storage/logs/inactive-profiles.log # Count deletions today grep "DELETED" storage/logs/inactive-profiles.log | grep "$(date +%Y-%m-%d)" | wc -l # Count warnings sent grep "warning_1 sent" storage/logs/inactive-profiles.log | grep "$(date +%Y-%m-%d)" | wc -l ``` ### Metrics to Track **Daily:** - Number of profiles marked inactive - Number of warnings sent (by level) - Number of profiles deleted - Number of profiles skipped (negative balance) **Weekly:** - Total inactive profiles - Inactive profiles by stage (warning 1, 2, final, pending deletion) - Average time to deletion - Reactivation rate (users who logged back in) **Monthly:** - Data retention compliance - Account balance disposal (by method) - User notification effectiveness - Error rates --- ## Best Practices ### 1. Communication Strategy **Before Implementation:** - Announce new auto-delete policy to all users - Update Terms of Service - Add FAQ explaining the process - Give 60-day grace period before first deletions **During Operation:** - Clear warning emails with specific dates - Multiple warnings with escalating urgency - Easy reactivation process (just login) - Contact information for questions ### 2. Data Protection **GDPR Compliance:** - Auto-delete supports "right to be forgotten" - Users can delete account anytime (no waiting) - Clear retention policy stated upfront - Audit trail via log files **Balance Protection:** - Never delete profiles with negative balances - Allow balance donation before deletion - Clear communication about balance disposal - Option to withdraw balance before inactivity ### 3. Testing Strategy **Before Production:** - [ ] Test full cycle with minute-based config - [ ] Verify all 3 warning emails send correctly - [ ] Test deletion with various balance states - [ ] Confirm transactions anonymize properly - [ ] Verify scheduler runs correctly - [ ] Test queue worker processes emails - [ ] Check logs for errors - [ ] Test reactivation flow **In Production:** - [ ] Monitor logs daily for first month - [ ] Review deletion counts weekly - [ ] Track reactivation rates - [ ] Collect user feedback on warnings - [ ] Adjust timing if needed ### 4. Gradual Rollout **Phase 1: Warnings Only (Month 1)** ```php 'days_after_inactive' => [ 'warning_1' => 7, 'warning_2' => 10, 'warning_final' => 14, 'run_delete' => 99999, // Never delete (testing) ], ``` Send warnings but don't delete. Monitor feedback. **Phase 2: Long Timeline (Month 2-3)** ```php 'days_after_inactive' => [ 'warning_1' => 30, 'warning_2' => 45, 'warning_final' => 60, 'run_delete' => 90, // 3 months from marked inactive ], ``` Very generous timeline while users adjust. **Phase 3: Normal Timeline (Month 4+)** ```php 'days_after_inactive' => [ 'warning_1' => 7, 'warning_2' => 10, 'warning_final' => 14, 'run_delete' => 15, ], ``` Standard production timeline. --- ## See Also - [PROFILE_DELETION_GRACE_PERIOD.md](PROFILE_DELETION_GRACE_PERIOD.md) - Grace period and profile restoration - [PROFILE_INACTIVE_CONFIG.md](PROFILE_INACTIVE_CONFIG.md) - Profile inactive system documentation - [EMAIL-TESTING-GUIDE.md](../EMAIL-TESTING-GUIDE.md) - Email testing procedures - `app/Console/Commands/MarkInactiveProfiles.php` - Mark inactive command - `app/Console/Commands/ProcessInactiveProfiles.php` - Process inactive command - `app/Console/Commands/PermanentlyDeleteExpiredProfiles.php` - Permanent deletion command - `app/Console/Commands/RestoreDeletedProfile.php` - Profile restoration command - `app/Actions/Jetstream/DeleteUser.php` - User deletion action - `app/Actions/Jetstream/RestoreProfile.php` - Profile restoration action - `app/Mail/UserDeletedMail.php` - Deletion confirmation email mailable - `app/Http/Livewire/Profile/DeleteUserForm.php` - Manual deletion form (uses same email) - `resources/views/emails/administration/{locale}/user-deleted.blade.php` - Deletion email template - `config/timebank-default.php` - Full configuration reference --- ## Changelog ### 2025-12-19 - Grace Period Implementation - Added 30-day grace period for deleted profiles - Profiles now soft-deleted first, permanently deleted after grace period - Created `profiles:permanently-delete-expired` scheduled command - Created `profiles:restore` command for profile restoration - Balance handling deferred until permanent deletion - Updated deletion emails to mention grace period - Added comprehensive grace period documentation ### 2025-11-18 - Deletion Confirmation Emails - Added automatic deletion confirmation emails - Integrated `UserDeletedMail` into `ProcessInactiveProfiles` command - Email sent immediately after successful profile deletion - Updated log messages to include "Confirmation email sent to {email}" - All deletion emails queued for asynchronous delivery ### 2025-11-14 - Initial Implementation - Created two-step auto-delete system - Implemented `MarkInactiveProfiles` command - Refactored `ProcessInactiveProfiles` to use `inactive_at` - Added scheduler configuration - Created comprehensive documentation - Fixed soft-delete exclusion bug - Added production and testing configurations --- **Last Updated:** 2025-12-19 **Maintainer:** Development Team **Status:** PRODUCTION READY **Commands:** `profiles:mark-inactive`, `profiles:process-inactive`, `profiles:permanently-delete-expired`, `profiles:restore`