sendOnce( 'welcome:tenant:10', 'welcome', 'tenant', ['user_id' => 10], 'ada@example.com', function () use (&$calls): void { $calls++; }, ); $second = $service->sendOnce( 'welcome:tenant:10', 'welcome', 'tenant', ['user_id' => 10], 'ada@example.com', function () use (&$calls): void { $calls++; }, ); $this->assertTrue($first); $this->assertFalse($second); $this->assertSame(1, $calls); $this->assertDatabaseHas('email_deliveries', [ 'idempotency_key' => 'welcome:tenant:10', 'status' => EmailDelivery::STATUS_SENT, 'attempts' => 1, ]); } public function test_it_records_a_failure_and_allows_a_retry(): void { $service = app(IdempotentEmailDeliveryService::class); try { $service->sendOnce( 'purchase-confirmed:20', 'purchase_confirmed', 'tenant', ['purchase_id' => 20], 'buyer@example.com', fn () => throw new RuntimeException('Sensitive SMTP detail'), ); $this->fail('The delivery exception was not rethrown.'); } catch (RuntimeException) { $this->assertDatabaseHas('email_deliveries', [ 'idempotency_key' => 'purchase-confirmed:20', 'status' => EmailDelivery::STATUS_FAILED, 'attempts' => 1, 'last_error' => RuntimeException::class, ]); } $sent = $service->sendOnce( 'purchase-confirmed:20', 'purchase_confirmed', 'tenant', ['purchase_id' => 20], 'buyer@example.com', static function (): void {}, ); $this->assertTrue($sent); $this->assertDatabaseHas('email_deliveries', [ 'idempotency_key' => 'purchase-confirmed:20', 'status' => EmailDelivery::STATUS_SENT, 'attempts' => 2, 'last_error' => null, ]); } public function test_it_recovers_an_expired_claim_but_not_an_active_one(): void { config(['mail.delivery_lease_seconds' => 300]); $service = app(IdempotentEmailDeliveryService::class); $delivery = EmailDelivery::query()->create([ 'idempotency_key' => 'password-reset:30', 'email_type' => 'password_reset', 'tenant_code' => 'tenant', 'status' => EmailDelivery::STATUS_PROCESSING, 'attempts' => 1, 'context' => ['attempt_id' => 30], 'recipient_fingerprint' => str_repeat('a', 64), 'claim_token' => fake()->uuid(), 'claimed_at' => now(), 'lease_expires_at' => now()->addMinute(), ]); $activeClaim = $service->sendOnce( $delivery->idempotency_key, $delivery->email_type, $delivery->tenant_code, $delivery->context, 'ada@example.com', static function (): void {}, ); $this->assertFalse($activeClaim); $delivery->update(['lease_expires_at' => now()->subSecond()]); $expiredClaim = $service->sendOnce( $delivery->idempotency_key, $delivery->email_type, $delivery->tenant_code, $delivery->context, 'ada@example.com', static function (): void {}, ); $this->assertTrue($expiredClaim); $this->assertDatabaseHas('email_deliveries', [ 'idempotency_key' => 'password-reset:30', 'status' => EmailDelivery::STATUS_SENT, 'attempts' => 2, ]); } }