feat(mail): implement MailService for tenant-specific SMTP configuration and update MailTestService to utilize it

This commit is contained in:
2026-07-21 16:43:09 -03:00
parent 22da4966e9
commit 3fe48fbfa5
5 changed files with 219 additions and 3 deletions

View File

@@ -0,0 +1,96 @@
<?php
namespace App\Domains\Mail\Services;
use App\Domains\Integration\Models\TenantIntegration;
use App\Domains\Tenant\Models\Tenant;
use Illuminate\Contracts\Mail\Factory as MailFactory;
use Illuminate\Contracts\Mail\Mailable;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Mail\MailManager;
use InvalidArgumentException;
class MailService
{
private const INTEGRATION_CODE = 'email';
private const REQUIRED_SMTP_FIELDS = [
'MAIL_HOST',
'MAIL_PORT',
'MAIL_USERNAME',
'MAIL_PASSWORD',
'MAIL_FROM_ADDRESS',
];
private readonly MailFactory $mailFactory;
private readonly ?TenantIntegration $emailIntegration;
private readonly Mailer $mailer;
public function __construct(
private readonly Tenant $tenant,
?MailFactory $mailFactory = null,
) {
$this->mailFactory = $mailFactory ?? app(MailFactory::class);
$this->emailIntegration = TenantIntegration::query()
->where('tenant_code', $this->tenant->codigo)
->where('integration_code', self::INTEGRATION_CODE)
->first();
$this->mailer = $this->resolveMailer();
}
public function send(string|array $recipient, Mailable $mail): void
{
$this->mailer->to($recipient)->send($mail);
}
public function mailerName(): string
{
return $this->emailIntegration ? 'tenant-smtp' : (string) config('mail.default');
}
private function resolveMailer(): Mailer
{
if (! $this->emailIntegration) {
return $this->mailFactory->mailer();
}
// MailFake implements the mail factory contract, but deliberately cannot
// build transports. Returning it keeps normal Mail::fake() assertions useful.
if (! $this->mailFactory instanceof MailManager) {
return $this->mailFactory->mailer();
}
$data = $this->emailIntegration->integration_data;
if (! is_array($data)) {
throw new InvalidArgumentException('La configuración SMTP del tenant no es válida.');
}
foreach (self::REQUIRED_SMTP_FIELDS as $field) {
if (! array_key_exists($field, $data) || $data[$field] === null || $data[$field] === '') {
throw new InvalidArgumentException("Falta {$field} en la configuración SMTP del tenant.");
}
}
$mailer = $this->mailFactory->build([
'name' => "tenant-smtp-{$this->tenant->codigo}",
'transport' => 'smtp',
'scheme' => $data['MAIL_SCHEME'] ?? null,
'host' => $data['MAIL_HOST'],
'port' => (int) $data['MAIL_PORT'],
'username' => $data['MAIL_USERNAME'],
'password' => $data['MAIL_PASSWORD'],
'timeout' => isset($data['MAIL_TIMEOUT']) ? (int) $data['MAIL_TIMEOUT'] : null,
'local_domain' => $data['MAIL_EHLO_DOMAIN'] ?? null,
]);
$mailer->alwaysFrom(
$data['MAIL_FROM_ADDRESS'],
$data['MAIL_FROM_NAME'] ?? $this->tenant->nombre,
);
return $mailer;
}
}

View File

@@ -2,9 +2,9 @@
namespace App\Domains\MailTest\Services;
use App\Domains\Mail\Services\MailService;
use App\Domains\MailTest\Mailables\TestMail;
use App\Domains\Tenant\Models\Tenant;
use Illuminate\Support\Facades\Mail;
class MailTestService
{
@@ -16,13 +16,14 @@ class MailTestService
$subject ??= 'Prueba de correo de Shopit';
$message ??= 'Este es un correo de prueba enviado desde Shopit.';
Mail::to($recipient)->send(new TestMail($subject, $message, $tenant));
$mailService = new MailService($tenant);
$mailService->send($recipient, new TestMail($subject, $message, $tenant));
return [
'message' => 'Correo de prueba enviado correctamente.',
'recipient' => $recipient,
'tenant_code' => $tenant->codigo,
'mailer' => (string) config('mail.default'),
'mailer' => $mailService->mailerName(),
'sent_at' => now()->toIso8601String(),
];
}