Files
timebank-cc-public/references/PROFILE_AUTO_DELETE.md
Ronald Huynen 2547717edb Initial commit
2026-03-23 21:37:59 +01:00

1140 lines
34 KiB
Markdown

# 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`