diff --git a/composer.json b/composer.json index 193bdc4..02166ba 100644 --- a/composer.json +++ b/composer.json @@ -25,16 +25,17 @@ "larastan/larastan": "^3.0", "laravel-notification-channels/discord": "^1.6", "laravel-notification-channels/telegram": "^4.0 || ^5.0 || ^6.0", - "laravel/slack-notification-channel": "^2.5 || ^3.3.2", "laravel/pint": "^1.17.0", + "laravel/slack-notification-channel": "^2.5 || ^3.3.2", "nunomaduro/collision": "^7.5.0|^8.4", "orchestra/testbench": "^8.5.0|^9.4.0", - "pestphp/pest-plugin-laravel": "^3.0", "pestphp/pest": "^3.0", + "pestphp/pest-plugin-laravel": "^3.0", "phpstan/extension-installer": "^1.4", "phpstan/phpstan-deprecation-rules": "^2.0", "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.0", + "resend/resend-laravel": "^0.17.0" }, "autoload": { "psr-4": { @@ -44,18 +45,37 @@ }, "autoload-dev": { "psr-4": { - "Vormkracht10\\Mails\\Tests\\": "tests" + "Vormkracht10\\Mails\\Tests\\": "tests", + "Workbench\\App\\": "workbench/app/", + "Workbench\\Database\\Factories\\": "workbench/database/factories/", + "Workbench\\Database\\Seeders\\": "workbench/database/seeders/" }, "files": [ "helpers.php" ] }, "scripts": { - "post-autoload-dump": "@php ./vendor/bin/testbench package:discover --ansi", + "post-autoload-dump": [ + "@clear", + "@prepare", + "@php ./vendor/bin/testbench package:discover --ansi" + ], "analyse": "vendor/bin/phpstan analyse", "test": "vendor/bin/pest", "test-coverage": "vendor/bin/pest --coverage", - "format": "vendor/bin/pint" + "format": "vendor/bin/pint", + "clear": "@php vendor/bin/testbench package:purge-skeleton --ansi", + "prepare": "@php vendor/bin/testbench package:discover --ansi", + "build": "@php vendor/bin/testbench workbench:build --ansi", + "serve": [ + "Composer\\Config::disableProcessTimeout", + "@build", + "@php vendor/bin/testbench serve --ansi" + ], + "lint": [ + "@php vendor/bin/pint --ansi", + "@php vendor/bin/phpstan analyse --verbose --ansi" + ] }, "config": { "sort-packages": true, diff --git a/src/Drivers/ResendDriver.php b/src/Drivers/ResendDriver.php new file mode 100644 index 0000000..8697890 --- /dev/null +++ b/src/Drivers/ResendDriver.php @@ -0,0 +1,128 @@ +trackingConfig = (array) config('mails.logging.tracking'); + } + + /** + * This method only checks that the Laravel Resend package is installed + * and configured as this package sets up webhook functionality + */ + public function registerWebhooks($components): void + { + /** + * Configuration for webhooks are only available in dashboard + */ + if (! class_exists('Resend\Laravel\ResendServiceProvider')) { + throw new LaravelResendException('Unable to find Laravel Resend Package'); + } + + if (empty(config('resend.webhook.secret'))) { + throw new LaravelResendException('Invalid Resend webhook secret'); + } + } + + public function verifyWebhookSignature(array $payload): bool + { + /** + * Using webhook created by laravel resend package. + */ + return false; + } + + public function attachUuidToMail(MessageSending $event, string $uuid): MessageSending + { + $event->message->getHeaders()->addTextHeader('X-Resend-Variables', json_encode([config('mails.headers.uuid') => $uuid])); + + return $event; + } + + public function getUuidFromPayload(array $payload): ?string + { + return $payload['data']['email_id']; + } + + protected function getTimestampFromPayload(array $payload): string + { + return $payload['data']['created_at'] ?? now(); + } + + public function eventMapping(): array + { + + return [ + EventType::ACCEPTED->value => ['type' => 'email.sent'], + EventType::CLICKED->value => ['type' => 'email.clicked'], + EventType::COMPLAINED->value => ['type' => 'email.complained'], + EventType::DELIVERED->value => ['type' => 'email.delivered'], + EventType::HARD_BOUNCED->value => ['type' => 'email.bounced'], + EventType::OPENED->value => ['type' => 'email.opened'], + EventType::SOFT_BOUNCED->value => ['type' => 'email.delivery_delayed'], + ]; + } + + public function dataMapping(): array + { + return [ + 'ip_address' => 'data.click.ipAddress', + 'link' => 'data.click.link', + 'user_agent' => 'data.click.userAgent', + ]; + } + + public function clicked(Mail $mail, string $timestamp): void + { + if (! empty($this->trackingConfig['clicks']) && $this->trackingConfig['clicks']) { + parent::clicked($mail, $timestamp); + } + } + + public function complained(Mail $mail, string $timestamp): void + { + if (! empty($this->trackingConfig['complaints']) && $this->trackingConfig['complaints']) { + parent::complained($mail, $timestamp); + } + } + + public function delivered(Mail $mail, string $timestamp): void + { + if (! empty($this->trackingConfig['deliveries']) && $this->trackingConfig['deliveries']) { + parent::delivered($mail, $timestamp); + } + } + + public function hardBounced(Mail $mail, string $timestamp): void + { + if (! empty($this->trackingConfig['bounces']) && $this->trackingConfig['bounces']) { + parent::hardBounced($mail, $timestamp); + } + } + + public function softBounced(Mail $mail, string $timestamp): void + { + if (! empty($this->trackingConfig['bounces']) && $this->trackingConfig['bounces']) { + parent::softBounced($mail, $timestamp); + } + } + + public function opened(Mail $mail, string $timestamp): void + { + if (! empty($this->trackingConfig['opened']) && $this->trackingConfig['opened']) { + parent::softBounced($mail, $timestamp); + } + } +} diff --git a/src/Enums/Provider.php b/src/Enums/Provider.php index 27126dc..4e0d357 100644 --- a/src/Enums/Provider.php +++ b/src/Enums/Provider.php @@ -6,4 +6,5 @@ enum Provider: string { case POSTMARK = 'postmark'; case MAILGUN = 'mailgun'; + case RESEND = 'resend'; } diff --git a/src/Exceptions/LaravelResendException.php b/src/Exceptions/LaravelResendException.php new file mode 100644 index 0000000..0168616 --- /dev/null +++ b/src/Exceptions/LaravelResendException.php @@ -0,0 +1,10 @@ +value, $event->payload); + } +} diff --git a/src/MailsServiceProvider.php b/src/MailsServiceProvider.php index db858f7..090ebf6 100644 --- a/src/MailsServiceProvider.php +++ b/src/MailsServiceProvider.php @@ -22,6 +22,7 @@ use Vormkracht10\Mails\Listeners\LogSendingMail; use Vormkracht10\Mails\Listeners\LogSentMail; use Vormkracht10\Mails\Listeners\NotifyOnBounce; +use Vormkracht10\Mails\Listeners\ResendLogMailEvent; use Vormkracht10\Mails\Listeners\StoreMailRelations; use Vormkracht10\Mails\Listeners\UnsuppressEmailAddress; use Vormkracht10\Mails\Managers\MailProviderManager; @@ -39,6 +40,16 @@ public function registeringPackage(): void $this->app['events']->listen(MessageSent::class, LogSentMail::class); $this->app['events']->listen(MailHardBounced::class, NotifyOnBounce::class); $this->app['events']->listen(MailUnsuppressed::class, UnsuppressEmailAddress::class); + + if (class_exists('Resend\Laravel\ResendServiceProvider')) { + $this->app['events']->listen('Resend\Laravel\Events\EmailSent', ResendLogMailEvent::class); + $this->app['events']->listen('Resend\Laravel\Events\EmailBounced', ResendLogMailEvent::class); + $this->app['events']->listen('Resend\Laravel\Events\EmailClicked', ResendLogMailEvent::class); + $this->app['events']->listen('Resend\Laravel\Events\EmailComplained', ResendLogMailEvent::class); + $this->app['events']->listen('Resend\Laravel\Events\EmailDelivered', ResendLogMailEvent::class); + $this->app['events']->listen('Resend\Laravel\Events\EmailDeliveredDelayed ', ResendLogMailEvent::class); + $this->app['events']->listen('Resend\Laravel\Events\EmailOpened', ResendLogMailEvent::class); + } } public function bootingPackage(): void diff --git a/src/Managers/MailProviderManager.php b/src/Managers/MailProviderManager.php index ba43c37..e576e62 100644 --- a/src/Managers/MailProviderManager.php +++ b/src/Managers/MailProviderManager.php @@ -5,6 +5,7 @@ use Illuminate\Support\Manager; use Vormkracht10\Mails\Drivers\MailgunDriver; use Vormkracht10\Mails\Drivers\PostmarkDriver; +use Vormkracht10\Mails\Drivers\ResendDriver; class MailProviderManager extends Manager { @@ -23,6 +24,11 @@ protected function createMailgunDriver(): MailgunDriver return new MailgunDriver; } + protected function createResendDriver(): ResendDriver + { + return new ResendDriver; + } + public function getDefaultDriver(): ?string { return null;