Skip to content

Commit 216a4dc

Browse files
committed
feat: introduce mail configuration management and SES event handling
- Added new services and DTOs to support dynamic mail configuration through `MailConfigurationService`. - Implemented `SettingsService` tests for encrypted settings storage and retrieval. - Introduced support for SES bounce and complaint webhook handling via `SesWebhookController`. - Created `SesUsageRecordModel` and migration for tracking SES usage metrics. - Integrated encrypted field support in settings pages for secure data storage.
1 parent 9fa765e commit 216a4dc

82 files changed

Lines changed: 5805 additions & 75 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Application\Mail\DTOs\Response;
6+
7+
final readonly class ConnectionTestResultDTO
8+
{
9+
public function __construct(
10+
public bool $success,
11+
public ?int $responseTimeMs = null,
12+
public ?string $errorMessage = null,
13+
public ?string $serverResponse = null,
14+
) {}
15+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Application\Mail\DTOs\Response;
6+
7+
final readonly class MailConfigurationResponseDTO
8+
{
9+
public function __construct(
10+
public bool $enabled,
11+
public string $driver,
12+
public string $fromAddress,
13+
public string $fromName,
14+
public ?string $smtpHost,
15+
public ?int $smtpPort,
16+
public ?string $smtpUsername,
17+
public bool $hasSmtpPassword,
18+
public ?string $smtpEncryption,
19+
public ?int $smtpTimeout,
20+
public ?string $sesRegion,
21+
public ?string $sesAccessKeyId,
22+
public bool $hasSesSecretAccessKey,
23+
public bool $hasResendApiKey,
24+
public ?int $quotaDailyLimit,
25+
public ?int $quotaMonthlyLimit,
26+
public ?int $quotaWarningThreshold,
27+
) {}
28+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Application\Mail\DTOs\Response;
6+
7+
final readonly class MailStatisticsResponseDTO
8+
{
9+
public function __construct(
10+
public int $sentToday,
11+
public int $sentThisMonth,
12+
public int $failedToday,
13+
public int $failedThisMonth,
14+
public float $deliveryRate,
15+
) {}
16+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Application\Mail\DTOs\Response;
6+
7+
final readonly class QuotaStatusResponseDTO
8+
{
9+
public function __construct(
10+
public int $dailyUsed,
11+
public int $dailyLimit,
12+
public int $monthlyUsed,
13+
public int $monthlyLimit,
14+
public float $percentageUsed,
15+
public bool $isWarning,
16+
public bool $isLimitReached,
17+
) {}
18+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Application\Mail\DTOs;
6+
7+
final readonly class UpdateMailConfigurationDTO
8+
{
9+
public function __construct(
10+
public bool $enabled,
11+
public string $driver,
12+
public string $fromAddress,
13+
public string $fromName,
14+
public ?string $smtpHost = null,
15+
public ?int $smtpPort = null,
16+
public ?string $smtpUsername = null,
17+
public ?string $smtpPassword = null,
18+
public ?string $smtpEncryption = null,
19+
public ?int $smtpTimeout = null,
20+
public ?string $sesRegion = null,
21+
public ?string $sesAccessKeyId = null,
22+
public ?string $sesSecretAccessKey = null,
23+
public ?string $resendApiKey = null,
24+
public ?int $quotaDailyLimit = null,
25+
public ?int $quotaMonthlyLimit = null,
26+
public ?int $quotaWarningThreshold = null,
27+
) {}
28+
29+
/**
30+
* @param array<string, mixed> $data
31+
*/
32+
public static function fromArray(array $data): self
33+
{
34+
return new self(
35+
enabled: (bool) ($data['mail_enabled'] ?? false),
36+
driver: (string) ($data['mail_driver'] ?? 'smtp'),
37+
fromAddress: (string) ($data['mail_from_address'] ?? ''),
38+
fromName: (string) ($data['mail_from_name'] ?? ''),
39+
smtpHost: isset($data['mail_smtp_host']) ? (string) $data['mail_smtp_host'] : null,
40+
smtpPort: isset($data['mail_smtp_port']) ? (int) $data['mail_smtp_port'] : null,
41+
smtpUsername: isset($data['mail_smtp_username']) ? (string) $data['mail_smtp_username'] : null,
42+
smtpPassword: isset($data['mail_smtp_password']) ? (string) $data['mail_smtp_password'] : null,
43+
smtpEncryption: isset($data['mail_smtp_encryption']) ? (string) $data['mail_smtp_encryption'] : null,
44+
smtpTimeout: isset($data['mail_smtp_timeout']) ? (int) $data['mail_smtp_timeout'] : null,
45+
sesRegion: isset($data['mail_ses_region']) ? (string) $data['mail_ses_region'] : null,
46+
sesAccessKeyId: isset($data['mail_ses_access_key_id']) ? (string) $data['mail_ses_access_key_id'] : null,
47+
sesSecretAccessKey: isset($data['mail_ses_secret_access_key']) ? (string) $data['mail_ses_secret_access_key'] : null,
48+
resendApiKey: isset($data['mail_resend_api_key']) ? (string) $data['mail_resend_api_key'] : null,
49+
quotaDailyLimit: isset($data['mail_quota_daily_limit']) ? (int) $data['mail_quota_daily_limit'] : null,
50+
quotaMonthlyLimit: isset($data['mail_quota_monthly_limit']) ? (int) $data['mail_quota_monthly_limit'] : null,
51+
quotaWarningThreshold: isset($data['mail_quota_warning_threshold']) ? (int) $data['mail_quota_warning_threshold'] : null,
52+
);
53+
}
54+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Application\Mail\Services;
6+
7+
use App\Application\Mail\DTOs\Response\QuotaStatusResponseDTO;
8+
9+
interface EmailQuotaServiceInterface
10+
{
11+
public function canSendEmail(): bool;
12+
13+
public function getQuotaStatus(): QuotaStatusResponseDTO;
14+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Application\Mail\Services;
6+
7+
use App\Application\Mail\DTOs\Response\MailConfigurationResponseDTO;
8+
use App\Application\Mail\DTOs\UpdateMailConfigurationDTO;
9+
use App\Domain\Mail\Enums\MailDriver;
10+
11+
interface MailConfigurationServiceInterface
12+
{
13+
/**
14+
* Get the current mail configuration (passwords masked).
15+
*/
16+
public function getConfiguration(): MailConfigurationResponseDTO;
17+
18+
/**
19+
* Update the mail configuration.
20+
*/
21+
public function updateConfiguration(UpdateMailConfigurationDTO $dto): void;
22+
23+
/**
24+
* Apply stored settings to Laravel's runtime config.
25+
*/
26+
public function applyToRuntime(): void;
27+
28+
/**
29+
* Check if mail sending is enabled.
30+
*/
31+
public function isMailEnabled(): bool;
32+
33+
/**
34+
* Get the currently active mail driver.
35+
*/
36+
public function getActiveDriver(): MailDriver;
37+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Application\Mail\Services;
6+
7+
use App\Application\Mail\DTOs\Response\MailStatisticsResponseDTO;
8+
9+
interface MailStatisticsServiceInterface
10+
{
11+
public function getStats(): MailStatisticsResponseDTO;
12+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Application\Mail\Services;
6+
7+
use App\Application\Mail\DTOs\Response\ConnectionTestResultDTO;
8+
9+
interface MailTestServiceInterface
10+
{
11+
/**
12+
* Test SMTP connection by opening a socket and performing EHLO.
13+
*/
14+
public function testSmtpConnection(): ConnectionTestResultDTO;
15+
16+
/**
17+
* Send a test email to verify the full mail pipeline.
18+
*/
19+
public function sendTestEmail(string $to): ConnectionTestResultDTO;
20+
}

src/app/Application/Services/SettingsServiceInterface.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ public function set(string $key, string $value): void;
2323
*/
2424
public function getLocationSettings(): LocationSettingsDTO;
2525

26+
/**
27+
* Get an encrypted setting, decrypting it on read.
28+
*/
29+
public function getEncrypted(string $key, mixed $default = null): mixed;
30+
31+
/**
32+
* Set a setting, encrypting the value before storage.
33+
*/
34+
public function setEncrypted(string $key, string $value): void;
35+
2636
/**
2737
* Clear the settings cache.
2838
*/

0 commit comments

Comments
 (0)