feat(mail-test): enhance email functionality with tenant branding and routing

This commit is contained in:
2026-07-21 16:26:46 -03:00
parent 1a05c380a0
commit 22da4966e9
7 changed files with 194 additions and 15 deletions

View File

@@ -4,6 +4,7 @@ namespace App\Domains\MailTest\Controllers;
use App\Domains\MailTest\Requests\SendTestMailRequest;
use App\Domains\MailTest\Services\MailTestService;
use App\Domains\Tenant\Models\Tenant;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
@@ -13,10 +14,15 @@ class MailTestController extends Controller
protected MailTestService $mailTestService,
) {}
public function __invoke(SendTestMailRequest $request): JsonResponse
public function __invoke(SendTestMailRequest $request, string $tenantCode): JsonResponse
{
$tenant = Tenant::query()
->where('codigo', $tenantCode)
->firstOrFail();
return response()->json(
$this->mailTestService->send(
$tenant,
$request->validated('to'),
$request->validated('subject'),
$request->validated('message'),

View File

@@ -2,6 +2,7 @@
namespace App\Domains\MailTest\Mailables;
use App\Domains\Tenant\Models\Tenant;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
@@ -15,6 +16,7 @@ class TestMail extends Mailable
public function __construct(
public readonly string $mailSubject,
public readonly string $mailMessage,
public readonly Tenant $tenant,
) {}
public function envelope(): Envelope
@@ -24,14 +26,15 @@ class TestMail extends Mailable
public function content(): Content
{
$message = nl2br(e($this->mailMessage));
$this->tenant->loadMissing(['headerLogo', 'footerLogo']);
return new Content(
htmlString: <<<HTML
<h1>Prueba de correo de Shopit</h1>
<p>{$message}</p>
<p><small>Si recibiste este mensaje, la configuracion de correo funciona correctamente.</small></p>
HTML,
view: 'mail.test',
with: [
'tenant' => $this->tenant,
'headerLogoUrl' => $this->tenant->headerLogo?->getTemporaryUrl(1440),
'footerLogoUrl' => $this->tenant->footerLogo?->getTemporaryUrl(1440),
],
);
}
}

View File

@@ -3,6 +3,7 @@
namespace App\Domains\MailTest\Services;
use App\Domains\MailTest\Mailables\TestMail;
use App\Domains\Tenant\Models\Tenant;
use Illuminate\Support\Facades\Mail;
class MailTestService
@@ -10,16 +11,17 @@ class MailTestService
/**
* @return array<string, string>
*/
public function send(string $recipient, ?string $subject = null, ?string $message = null): array
public function send(Tenant $tenant, string $recipient, ?string $subject = null, ?string $message = null): array
{
$subject ??= 'Prueba de correo de Shopit';
$message ??= 'Este es un correo de prueba enviado desde Shopit.';
Mail::to($recipient)->send(new TestMail($subject, $message));
Mail::to($recipient)->send(new TestMail($subject, $message, $tenant));
return [
'message' => 'Correo de prueba enviado correctamente.',
'recipient' => $recipient,
'tenant_code' => $tenant->codigo,
'mailer' => (string) config('mail.default'),
'sent_at' => now()->toIso8601String(),
];

View File

@@ -3,4 +3,4 @@
use App\Domains\MailTest\Controllers\MailTestController;
use Illuminate\Support\Facades\Route;
Route::post('mail-test/send', MailTestController::class);
Route::post('{tenant_code}/mail-test/send', MailTestController::class);

View File

@@ -0,0 +1,49 @@
@props(['tenant', 'headerLogoUrl' => null, 'footerLogoUrl' => null])
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light">
<title>{{ $tenant->nombre }}</title>
<style>
@media only screen and (max-width: 620px) {
.mail-container { width: 100% !important; }
.mail-content { padding: 32px 24px !important; }
}
</style>
</head>
<body style="margin: 0; padding: 0; background-color: #f1f5f9; color: #334155; font-family: Arial, Helvetica, sans-serif;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background-color: #f1f5f9;">
<tr>
<td align="center" style="padding: 32px 12px;">
<table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0" class="mail-container" style="width: 600px; max-width: 600px; background-color: #ffffff; border-top: 4px solid {{ $tenant->primary_color }}; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08);">
<tr>
<td align="center" bgcolor="{{ $tenant->header_bg_color }}" style="padding: 24px 32px; background-color: {{ $tenant->header_bg_color }};">
@if ($headerLogoUrl)
<img src="{{ $headerLogoUrl }}" alt="{{ $tenant->nombre }}" width="180" style="display: block; width: auto; max-width: 180px; max-height: 64px; border: 0;">
@else
<span style="color: {{ $tenant->primary_color }}; font-size: 24px; font-weight: 700; line-height: 1.2;">{{ $tenant->nombre }}</span>
@endif
</td>
</tr>
<tr>
<td class="mail-content" style="padding: 40px 48px; font-size: 16px; line-height: 1.6;">
{{ $slot }}
</td>
</tr>
<tr>
<td align="center" bgcolor="{{ $tenant->footer_bg_color }}" style="padding: 24px 32px; background-color: {{ $tenant->footer_bg_color }}; color: #ffffff; font-size: 12px; line-height: 1.5;">
@if ($footerLogoUrl)
<img src="{{ $footerLogoUrl }}" alt="{{ $tenant->nombre }}" width="140" style="display: block; width: auto; max-width: 140px; max-height: 48px; margin: 0 auto 16px; border: 0;">
@endif
{{ $footer ?? 'Este correo fue enviado por '.$tenant->nombre.'.' }}
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@@ -0,0 +1,11 @@
<x-mail.branded-layout :tenant="$tenant" :header-logo-url="$headerLogoUrl" :footer-logo-url="$footerLogoUrl">
<h1 style="margin: 0 0 20px; color: {{ $tenant->primary_color }}; font-size: 26px; line-height: 1.3;">
Prueba de correo de Shopit
</h1>
<p style="margin: 0 0 16px;">{!! nl2br(e($mailMessage)) !!}</p>
<p style="margin: 24px 0 0; color: #64748b; font-size: 13px;">
Si recibiste este mensaje, la configuración de correo funciona correctamente.
</p>
</x-mail.branded-layout>

View File

@@ -2,17 +2,24 @@
namespace Tests\Feature\MailTest;
use App\Domains\Attachable\Enums\AttachmentType;
use App\Domains\Attachable\Models\Attachment;
use App\Domains\MailTest\Mailables\TestMail;
use App\Domains\Tenant\Models\Tenant;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;
class MailTestControllerTest extends TestCase
{
use RefreshDatabase;
public function test_it_sends_a_test_email(): void
{
Mail::fake();
$tenant = $this->createTenant();
$response = $this->postJson('/api/mail-test/send', [
$response = $this->postJson('/api/acme/mail-test/send', [
'to' => 'recipient@example.com',
'subject' => 'SMTP test',
'message' => 'Test message',
@@ -21,21 +28,24 @@ class MailTestControllerTest extends TestCase
$response->assertOk()
->assertJsonPath('message', 'Correo de prueba enviado correctamente.')
->assertJsonPath('recipient', 'recipient@example.com')
->assertJsonPath('tenant_code', 'acme')
->assertJsonPath('mailer', 'array')
->assertJsonStructure(['sent_at']);
Mail::assertSent(TestMail::class, function (TestMail $mail): bool {
Mail::assertSent(TestMail::class, function (TestMail $mail) use ($tenant): bool {
return $mail->hasTo('recipient@example.com')
&& $mail->mailSubject === 'SMTP test'
&& $mail->mailMessage === 'Test message';
&& $mail->mailMessage === 'Test message'
&& $mail->tenant->is($tenant);
});
}
public function test_it_uses_default_content_when_optional_fields_are_omitted(): void
{
Mail::fake();
$this->createTenant();
$this->postJson('/api/mail-test/send', [
$this->postJson('/api/acme/mail-test/send', [
'to' => 'recipient@example.com',
])->assertOk();
@@ -48,12 +58,110 @@ class MailTestControllerTest extends TestCase
public function test_it_validates_the_recipient(): void
{
Mail::fake();
$this->createTenant();
$this->postJson('/api/mail-test/send', [
$this->postJson('/api/acme/mail-test/send', [
'to' => 'invalid-email',
])->assertUnprocessable()
->assertJsonValidationErrors(['to']);
Mail::assertNothingSent();
}
public function test_the_mail_template_uses_the_tenant_branding(): void
{
$tenant = new Tenant([
'codigo' => 'tenant-store',
'nombre' => 'Tenant Store',
'primary_color' => '#778899',
'header_bg_color' => '#112233',
'footer_bg_color' => '#445566',
]);
$tenant->setRelation('headerLogo', new class extends Attachment
{
public function getTemporaryUrl(int $expiresInMinutes = 10): string
{
return 'https://example.com/header-logo.png';
}
});
$tenant->setRelation('footerLogo', new class extends Attachment
{
public function getTemporaryUrl(int $expiresInMinutes = 10): string
{
return 'https://example.com/footer-logo.png';
}
});
$mail = new TestMail(
'Branded email',
'Tenant message',
$tenant,
);
$html = $mail->render();
$this->assertStringContainsString('https://example.com/header-logo.png', $html);
$this->assertStringContainsString('https://example.com/footer-logo.png', $html);
$this->assertStringContainsString('background-color: #112233', $html);
$this->assertStringContainsString('background-color: #445566', $html);
$this->assertStringContainsString('color: #778899', $html);
$this->assertStringContainsString('Tenant Store', $html);
$this->assertStringContainsString('Tenant message', $html);
}
public function test_it_returns_not_found_for_an_unknown_tenant(): void
{
Mail::fake();
$this->postJson('/api/unknown/mail-test/send', [
'to' => 'recipient@example.com',
])->assertNotFound();
Mail::assertNothingSent();
}
public function test_it_does_not_resolve_the_tenant_by_id(): void
{
Mail::fake();
$tenant = $this->createTenant();
$this->postJson("/api/{$tenant->id}/mail-test/send", [
'to' => 'recipient@example.com',
])->assertNotFound();
Mail::assertNothingSent();
}
private function createTenant(): Tenant
{
$headerLogo = Attachment::create([
'path' => 'tenants/header.png',
'filename' => 'header.png',
'type' => AttachmentType::Image,
'mime_type' => 'image/png',
'extension' => 'png',
]);
$footerLogo = Attachment::create([
'path' => 'tenants/footer.png',
'filename' => 'footer.png',
'type' => AttachmentType::Image,
'mime_type' => 'image/png',
'extension' => 'png',
]);
return Tenant::create([
'codigo' => 'acme',
'nombre' => 'Acme Store',
'dominio' => 'acme.example.com',
'primary_color' => '#778899',
'secondary_color' => '#64748b',
'danger_color' => '#dc2626',
'success_color' => '#16a34a',
'header_bg_color' => '#112233',
'footer_bg_color' => '#445566',
'header_logo_id' => $headerLogo->id,
'footer_logo_id' => $footerLogo->id,
]);
}
}