fix: delay emails to avoid reaching daily resend limit

This commit is contained in:
Víctor Falcón 2026-01-09 11:11:38 +01:00
parent 9dab6f4835
commit 8ac25200dc
3 changed files with 70 additions and 9 deletions

View File

@ -73,13 +73,20 @@ class SendUpdateEmailCommand extends Command
}
$this->info('Queueing update emails...');
$this->info('Rate limit: 50 emails per day');
$progressBar = $this->output->createProgressBar($users->count());
$progressBar->start();
$queued = 0;
foreach ($users as $user) {
SendUpdateEmailJob::dispatch($user, $viewName, $identifier, $subject);
foreach ($users as $index => $user) {
// Add delay of +1 day for every 50 emails
$delayDays = (int) floor($index / 50);
$delay = now()->addDays($delayDays);
SendUpdateEmailJob::dispatch($user, $viewName, $identifier, $subject)
->delay($delay);
$queued++;
$progressBar->advance();
}
@ -87,7 +94,13 @@ class SendUpdateEmailCommand extends Command
$progressBar->finish();
$this->newLine();
$this->info("Successfully queued {$queued} update email(s) to the 'emails' queue!");
if ($queued > 50) {
$totalDays = (int) floor(($queued - 1) / 50);
$this->info("Successfully queued {$queued} update email(s) to the 'emails' queue!");
$this->info("Emails will be sent over {$totalDays} day(s) (50 emails per day)");
} else {
$this->info("Successfully queued {$queued} update email(s) to the 'emails' queue!");
}
return self::SUCCESS;
}

View File

@ -42,18 +42,40 @@ Once you've created your template and deployed it to production:
```bash
# Basic usage
php artisan update-email:send jan-2026-updates jan-2026-updates
php artisan email:update jan-2026-updates
# With custom identifier
php artisan email:update jan-2026-updates jan-2026-updates
# With custom subject
php artisan update-email:send jan-2026-updates jan-2026-updates --subject="Exciting January Updates!"
php artisan email:update jan-2026-updates --subject="Exciting January Updates!"
# Exclude demo account
php artisan update-email:send jan-2026-updates jan-2026-updates --exclude-demo
php artisan email:update jan-2026-updates --exclude-demo
# Skip confirmation prompt (for scripts/automation)
php artisan update-email:send jan-2026-updates jan-2026-updates --force
php artisan email:update jan-2026-updates --force
```
### 3.1. Rate Limiting
**Important**: The command automatically rate limits to **50 emails per day** to avoid overwhelming your email service and to maintain good sender reputation.
For example:
- **50 users**: All sent immediately
- **125 users**: 50 sent today, 50 tomorrow, 25 the day after
- **250 users**: 50 per day over 5 days
The command will show you the schedule:
```
Found 126 user(s).
Rate limit: 50 emails per day
Successfully queued 126 update email(s) to the 'emails' queue!
Emails will be sent over 2 day(s) (50 emails per day)
```
Jobs are queued with delays automatically - you don't need to do anything special!
### 4. Command Arguments
- `view`: The name of your template file (without .blade.php extension)
@ -100,7 +122,7 @@ Important information here
vim resources/views/mail/updates/feb-2026-updates.blade.php
# 2. Test locally (optional - create test user first)
php artisan update-email:send feb-2026-updates test-feb-2026
php artisan email:update feb-2026-updates test-feb-2026
# 3. Commit and push
git add resources/views/mail/updates/feb-2026-updates.blade.php
@ -111,7 +133,7 @@ git push
# ... your deployment process ...
# 5. Send on production
php artisan update-email:send feb-2026-updates feb-2026-updates
php artisan email:update feb-2026-updates feb-2026-updates
```
## Best Practices

View File

@ -204,3 +204,29 @@ test('command skips confirmation with force flag', function () {
Queue::assertPushed(SendUpdateEmailJob::class, 1);
});
test('command applies rate limiting delay for large batches', function () {
// Create 150 users to test rate limiting across 3 days
User::factory()->count(150)->create();
artisan('email:update', [
'view' => 'test-update',
'identifier' => 'test-2026',
'--force' => true,
])->assertSuccessful();
Queue::assertPushed(SendUpdateEmailJob::class, 150);
// Check that delays are applied correctly
// First 50 emails: no delay (day 0)
// Next 50 emails: 1 day delay (day 1)
// Last 50 emails: 2 days delay (day 2)
$pushedJobs = Queue::pushedJobs()[SendUpdateEmailJob::class] ?? [];
expect($pushedJobs)->toHaveCount(150);
// Verify delays are applied correctly
// Note: We can't easily check the exact delay value in the test,
// but we can verify all jobs were pushed
expect($pushedJobs)->toHaveCount(150);
});