From fb8e55821498b17e476761258da7dca649ab685a Mon Sep 17 00:00:00 2001 From: Mark van Eijk Date: Tue, 24 Mar 2026 20:51:53 +0100 Subject: [PATCH 1/2] feat: populate mail_class column from Laravel's Mailable data Laravel's Mailable class already provides the FQCN via $event->data['__laravel_mailable']. Read this value in LogMail instead of the commented-out nonexistent method. Also adds the missing scopeForUuid and scopeForMailClass query scopes that were declared in the model's docblock. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/Actions/LogMail.php | 2 +- src/Models/Mail.php | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Actions/LogMail.php b/src/Actions/LogMail.php index dbb8b53..5a39ede 100644 --- a/src/Actions/LogMail.php +++ b/src/Actions/LogMail.php @@ -98,7 +98,7 @@ public function getMandatoryAttributes(MessageSending | MessageSent $event): arr { return [ 'uuid' => $this->getCustomUuid($event), - // 'mail_class' => $this->getMailClassHeaderValue($event), + 'mail_class' => $event->data['__laravel_mailable'] ?? null, 'sent_at' => $event instanceof MessageSent ? now() : null, 'mailer' => $event->data['mailer'], 'stream_id' => $this->getStreamId($event), diff --git a/src/Models/Mail.php b/src/Models/Mail.php index aa6a825..a5aa32d 100644 --- a/src/Models/Mail.php +++ b/src/Models/Mail.php @@ -198,6 +198,16 @@ public function scopeUnsent(Builder $builder): Builder return $builder->whereNull('sent_at'); } + public function scopeForUuid(Builder $builder, string $uuid): Builder + { + return $builder->where('uuid', $uuid); + } + + public function scopeForMailClass(Builder $builder, string $mailClass): Builder + { + return $builder->where('mail_class', $mailClass); + } + protected function status(): Attribute { return Attribute::make(get: function () { From 571af91883e130ab79ecca7fd9e4bed25b5cb1e2 Mon Sep 17 00:00:00 2001 From: markvaneijk <1925388+markvaneijk@users.noreply.github.com> Date: Tue, 24 Mar 2026 19:52:16 +0000 Subject: [PATCH 2/2] Fix styling --- rector.php | 4 ++-- src/Actions/AttachUuid.php | 4 ++-- src/Actions/LogMail.php | 14 +++++++------- src/Commands/CheckBounceRateCommand.php | 2 +- src/Commands/ResendMailCommand.php | 2 +- src/Drivers/MailgunDriver.php | 6 +++--- src/Drivers/PostmarkDriver.php | 4 ++-- src/Listeners/StoreMailRelations.php | 2 +- src/MailsServiceProvider.php | 2 +- src/Models/MailAttachment.php | 2 +- src/Models/MailEvent.php | 2 +- src/Notifications/SpamComplaintNotification.php | 2 +- tests/TestCase.php | 4 ++-- 13 files changed, 25 insertions(+), 25 deletions(-) diff --git a/rector.php b/rector.php index 42267ed..3766305 100644 --- a/rector.php +++ b/rector.php @@ -11,8 +11,8 @@ return static function (RectorConfig $rectorConfig): void { $rectorConfig->paths([ - __DIR__ . '/src', - __DIR__ . '/tests', + __DIR__.'/src', + __DIR__.'/tests', ]); $rectorConfig->sets([ diff --git a/src/Actions/AttachUuid.php b/src/Actions/AttachUuid.php index 10d7c56..4f5c066 100644 --- a/src/Actions/AttachUuid.php +++ b/src/Actions/AttachUuid.php @@ -28,7 +28,7 @@ public function handle(MessageSending $messageSending): MessageSending public function getProvider(MessageSending $messageSending): string { - return config('mail.mailers.' . $messageSending->data['mailer'] . '.transport') ?? $messageSending->data['mailer']; + return config('mail.mailers.'.$messageSending->data['mailer'].'.transport') ?? $messageSending->data['mailer']; } public function shouldTrackMails(string $provider): bool @@ -39,7 +39,7 @@ public function shouldTrackMails(string $provider): bool public function driverExistsForProvider(string $provider): bool { - return class_exists('Backstage\\Mails\\Drivers\\' . ucfirst($provider) . 'Driver'); + return class_exists('Backstage\\Mails\\Drivers\\'.ucfirst($provider).'Driver'); } public function trackingEnabled(): bool diff --git a/src/Actions/LogMail.php b/src/Actions/LogMail.php index 5a39ede..0b581d1 100644 --- a/src/Actions/LogMail.php +++ b/src/Actions/LogMail.php @@ -15,7 +15,7 @@ class LogMail { use AsAction; - public function handle(MessageSending | MessageSent $event): mixed + public function handle(MessageSending|MessageSent $event): mixed { if (! config('mails.logging.enabled')) { return null; @@ -49,14 +49,14 @@ public function newMailModelInstance() return new $model; } - public function getOnlyConfiguredAttributes(MessageSending | MessageSent $event): array + public function getOnlyConfiguredAttributes(MessageSending|MessageSent $event): array { return collect($this->getDefaultLogAttributes($event)) ->only($this->getConfiguredAttributes()) ->merge($this->getMandatoryAttributes($event)) ->merge([ 'mailer' => $event->data['mailer'], - 'transport' => config('mail.mailers.' . $event->data['mailer'] . '.transport'), + 'transport' => config('mail.mailers.'.$event->data['mailer'].'.transport'), ]) ->toArray(); } @@ -66,7 +66,7 @@ public function getConfiguredAttributes(): array return (array) config('mails.logging.attributes'); } - public function getDefaultLogAttributes(MessageSending | MessageSent $event): array + public function getDefaultLogAttributes(MessageSending|MessageSent $event): array { return [ 'subject' => $event->message->getSubject(), @@ -81,7 +81,7 @@ public function getDefaultLogAttributes(MessageSending | MessageSent $event): ar ]; } - protected function getStreamId(MessageSending | MessageSent $event): ?string + protected function getStreamId(MessageSending|MessageSent $event): ?string { if ($event->data['mailer'] !== Provider::POSTMARK) { return null; @@ -94,7 +94,7 @@ protected function getStreamId(MessageSending | MessageSent $event): ?string return config('mail.mailers.postmark.message_stream_id', 'outbound'); } - public function getMandatoryAttributes(MessageSending | MessageSent $event): array + public function getMandatoryAttributes(MessageSending|MessageSent $event): array { return [ 'uuid' => $this->getCustomUuid($event), @@ -105,7 +105,7 @@ public function getMandatoryAttributes(MessageSending | MessageSent $event): arr ]; } - protected function getCustomUuid(MessageSending | MessageSent $event): ?string + protected function getCustomUuid(MessageSending|MessageSent $event): ?string { if (! $event->message->getHeaders()->has(config('mails.headers.uuid'))) { return null; diff --git a/src/Commands/CheckBounceRateCommand.php b/src/Commands/CheckBounceRateCommand.php index 4a756fb..020866a 100644 --- a/src/Commands/CheckBounceRateCommand.php +++ b/src/Commands/CheckBounceRateCommand.php @@ -10,7 +10,7 @@ class CheckBounceRateCommand extends Command { protected $signature = 'mail:bounce-rate'; - protected $description = 'Check if the bounce rate is higher than the configured limit ' . + protected $description = 'Check if the bounce rate is higher than the configured limit '. 'and send a notification if it is.'; public function handle(): int diff --git a/src/Commands/ResendMailCommand.php b/src/Commands/ResendMailCommand.php index e67fc9d..bb6e86b 100644 --- a/src/Commands/ResendMailCommand.php +++ b/src/Commands/ResendMailCommand.php @@ -20,7 +20,7 @@ public function handle(): int { $uuid = $this->argument('uuid'); - $mail = mail::where('uuid', $uuid)->first(); + $mail = Mail::where('uuid', $uuid)->first(); if (is_null($mail)) { $this->components->error("Mail with uuid: \"{$uuid}\" does not exist"); diff --git a/src/Drivers/MailgunDriver.php b/src/Drivers/MailgunDriver.php index fcbd955..ea49164 100644 --- a/src/Drivers/MailgunDriver.php +++ b/src/Drivers/MailgunDriver.php @@ -83,7 +83,7 @@ public function verifyWebhookSignature(array $payload): bool return false; } - $hmac = hash_hmac('sha256', $payload['signature']['timestamp'] . $payload['signature']['token'], (string) config('services.mailgun.webhook_signing_key')); + $hmac = hash_hmac('sha256', $payload['signature']['timestamp'].$payload['signature']['token'], (string) config('services.mailgun.webhook_signing_key')); if (function_exists('hash_equals')) { return hash_equals($hmac, $payload['signature']['signature']); @@ -142,8 +142,8 @@ public function unsuppressEmailAddress(string $address, ?int $stream_id = null): { $pendingRequest = Http::asJson() ->withBasicAuth('api', config('services.mailgun.secret')) - ->baseUrl(config('services.mailgun.endpoint') . '/v3/'); + ->baseUrl(config('services.mailgun.endpoint').'/v3/'); - return $pendingRequest->delete(config('services.mailgun.domain') . '/unsubscribes/' . $address); + return $pendingRequest->delete(config('services.mailgun.domain').'/unsubscribes/'.$address); } } diff --git a/src/Drivers/PostmarkDriver.php b/src/Drivers/PostmarkDriver.php index 8dd1ae2..5c4bc26 100644 --- a/src/Drivers/PostmarkDriver.php +++ b/src/Drivers/PostmarkDriver.php @@ -105,7 +105,7 @@ public function verifyWebhookSignature(array $payload): bool public function attachUuidToMail(MessageSending $messageSending, string $uuid): MessageSending { - $messageSending->message->getHeaders()->addTextHeader('X-PM-Metadata-' . config('mails.headers.uuid'), $uuid); + $messageSending->message->getHeaders()->addTextHeader('X-PM-Metadata-'.config('mails.headers.uuid'), $uuid); return $messageSending; } @@ -182,7 +182,7 @@ public function unsuppressEmailAddress(string $address, ?int $stream_id = null): ]) ->baseUrl('https://api.postmarkapp.com/'); - return $pendingRequest->post('message-streams/' . $stream_id . '/suppressions/delete', [ + return $pendingRequest->post('message-streams/'.$stream_id.'/suppressions/delete', [ 'Suppressions' => [['emailAddress' => $address]], ]); } diff --git a/src/Listeners/StoreMailRelations.php b/src/Listeners/StoreMailRelations.php index cca06b6..874cdd5 100644 --- a/src/Listeners/StoreMailRelations.php +++ b/src/Listeners/StoreMailRelations.php @@ -36,7 +36,7 @@ protected function shouldAssociateModels(Email $email): bool ); } - protected function getAssociatedModels(Email $email): array | false + protected function getAssociatedModels(Email $email): array|false { $encrypted = $this->getHeaderBody( $email, diff --git a/src/MailsServiceProvider.php b/src/MailsServiceProvider.php index 04f89d8..66e6d94 100644 --- a/src/MailsServiceProvider.php +++ b/src/MailsServiceProvider.php @@ -68,7 +68,7 @@ public function configurePackage(Package $package): void */ protected function getMigrations(): array { - return collect(app(Filesystem::class)->files(__DIR__ . '/../database/migrations')) + return collect(app(Filesystem::class)->files(__DIR__.'/../database/migrations')) ->map(fn (SplFileInfo $file): string => str_replace('.php.stub', '', $file->getBasename())) ->toArray(); } diff --git a/src/Models/MailAttachment.php b/src/Models/MailAttachment.php index 8e97f17..e901600 100644 --- a/src/Models/MailAttachment.php +++ b/src/Models/MailAttachment.php @@ -60,7 +60,7 @@ public function mail(): BelongsTo protected function storagePath(): Attribute { - return Attribute::make(get: fn (): string => rtrim((string) config('mails.logging.attachments.root'), '/') . '/' . $this->getKey() . '/' . $this->filename); + return Attribute::make(get: fn (): string => rtrim((string) config('mails.logging.attachments.root'), '/').'/'.$this->getKey().'/'.$this->filename); } protected function fileData(): Attribute diff --git a/src/Models/MailEvent.php b/src/Models/MailEvent.php index 5a7f9da..ef27c33 100644 --- a/src/Models/MailEvent.php +++ b/src/Models/MailEvent.php @@ -98,6 +98,6 @@ public function scopeSuppressed(Builder $builder): void protected function eventClass(): Attribute { - return Attribute::make(get: fn (): string => 'Backstage\Mails\Events\Mail' . Str::studly($this->type->value)); + return Attribute::make(get: fn (): string => 'Backstage\Mails\Events\Mail'.Str::studly($this->type->value)); } } diff --git a/src/Notifications/SpamComplaintNotification.php b/src/Notifications/SpamComplaintNotification.php index 86bdd25..08a7b38 100644 --- a/src/Notifications/SpamComplaintNotification.php +++ b/src/Notifications/SpamComplaintNotification.php @@ -29,7 +29,7 @@ public function getMessage(): string '🔥', '🧯', '‼️', '⁉️', '🔴', '📣', '😅', '🥵', ]); - return $emoji . ' mail has bounced'; + return $emoji.' mail has bounced'; } public function toDiscord(): DiscordMessage diff --git a/tests/TestCase.php b/tests/TestCase.php index 40d2cf3..7d59d1b 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -18,7 +18,7 @@ protected function setUp(): void parent::setUp(); Factory::guessFactoryNamesUsing( - fn (string $modelName): string => 'Backstage\\Mails\\Database\\Factories\\' . class_basename($modelName) . 'Factory' + fn (string $modelName): string => 'Backstage\\Mails\\Database\\Factories\\'.class_basename($modelName).'Factory' ); $this->loadMigrations(); @@ -57,7 +57,7 @@ public function getEnvironmentSetUp($app): void protected function loadMigrations(): void { $filesystem = new Filesystem; - $migrationFiles = $filesystem->files(__DIR__ . '/../database/migrations/'); + $migrationFiles = $filesystem->files(__DIR__.'/../database/migrations/'); // Sorting to ensure migrations run in the correct order usort($migrationFiles, fn ($a, $b): int => strcmp((string) $a->getFilename(), (string) $b->getFilename()));