From 0bfad7f42ebba2a70aa6e08b211f3694a1ae3d77 Mon Sep 17 00:00:00 2001 From: Ionut Antohi Date: Tue, 20 Jan 2026 15:41:27 +0200 Subject: [PATCH 01/13] allow null --- src/Email.php | 4 ++-- src/Events/EmailSendingFailed.php | 2 +- src/Models/EmailModel.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Email.php b/src/Email.php index 2354158..7ca66f9 100644 --- a/src/Email.php +++ b/src/Email.php @@ -356,7 +356,7 @@ public function enqueue() * * @throws \Exception */ - public function dispatch(Carbon $delay = null) + public function dispatch(?Carbon $delay = null) { if (!$this->model) { throw new Exception('There is no email to be dispatched.'); @@ -371,7 +371,7 @@ public function dispatch(Carbon $delay = null) * @return static * @throws \Exception */ - public function send(Carbon $delay = null) + public function send(?Carbon $delay = null) { return $this->enqueue()->dispatch($delay); } diff --git a/src/Events/EmailSendingFailed.php b/src/Events/EmailSendingFailed.php index b77105c..fadc973 100644 --- a/src/Events/EmailSendingFailed.php +++ b/src/Events/EmailSendingFailed.php @@ -20,7 +20,7 @@ class EmailSendingFailed * @param \TsfCorp\Email\Models\EmailModel $email * @param \Throwable|null $exception */ - public function __construct(EmailModel $email, Throwable $exception = null) + public function __construct(EmailModel $email, ?Throwable $exception = null) { $this->email = $email; $this->exception = $exception; diff --git a/src/Models/EmailModel.php b/src/Models/EmailModel.php index dc08f6d..00c2338 100644 --- a/src/Models/EmailModel.php +++ b/src/Models/EmailModel.php @@ -107,7 +107,7 @@ public function getMetadata() * Dispatches a job for current record * @param \Carbon\Carbon|null $delay */ - public function dispatchJob(Carbon $delay = null) + public function dispatchJob(?Carbon $delay = null) { $this->status = EmailModel::STATUS_QUEUED; $this->save(); From 0f91d53f3b7cab6857c909b8f9422c30e840aa0d Mon Sep 17 00:00:00 2001 From: Ionut Antohi Date: Tue, 20 Jan 2026 15:45:03 +0200 Subject: [PATCH 02/13] remove docs --- src/Events/EmailClicked.php | 20 +++----------------- src/Events/EmailComplained.php | 20 +++----------------- src/Events/EmailDelivered.php | 20 +++----------------- src/Events/EmailFailed.php | 26 ++++---------------------- src/Events/EmailOpened.php | 20 +++----------------- src/Events/EmailSendingFailed.php | 14 ++------------ src/Events/EmailSendingSucceeded.php | 9 +-------- src/Events/EmailUnsubscribed.php | 20 +++----------------- 8 files changed, 22 insertions(+), 127 deletions(-) diff --git a/src/Events/EmailClicked.php b/src/Events/EmailClicked.php index 5c65f6f..46f97e9 100644 --- a/src/Events/EmailClicked.php +++ b/src/Events/EmailClicked.php @@ -7,24 +7,10 @@ class EmailClicked { - /** - * @var \TsfCorp\Email\Models\EmailModel - */ - public $email; - /** - * @var \TsfCorp\Email\Models\EmailRecipient - */ - public $recipient; - /** - * @var mixed - */ - public $payload; + public EmailModel $email; + public EmailRecipient $recipient; + public mixed $payload; - /** - * @param \TsfCorp\Email\Models\EmailModel $email - * @param \TsfCorp\Email\Models\EmailRecipient $recipient - * @param null $payload - */ public function __construct(EmailModel $email, EmailRecipient $recipient, mixed $payload = null) { $this->email = $email; diff --git a/src/Events/EmailComplained.php b/src/Events/EmailComplained.php index 808ab5a..935b4b1 100644 --- a/src/Events/EmailComplained.php +++ b/src/Events/EmailComplained.php @@ -7,24 +7,10 @@ class EmailComplained { - /** - * @var \TsfCorp\Email\Models\EmailModel - */ - public $email; - /** - * @var \TsfCorp\Email\Models\EmailRecipient - */ - public $recipient; - /** - * @var mixed - */ - public $payload; + public EmailModel $email; + public EmailRecipient $recipient; + public mixed $payload; - /** - * @param \TsfCorp\Email\Models\EmailModel $email - * @param \TsfCorp\Email\Models\EmailRecipient $recipient - * @param null $payload - */ public function __construct(EmailModel $email, EmailRecipient $recipient, mixed $payload = null) { $this->email = $email; diff --git a/src/Events/EmailDelivered.php b/src/Events/EmailDelivered.php index 6d77362..3459472 100644 --- a/src/Events/EmailDelivered.php +++ b/src/Events/EmailDelivered.php @@ -7,24 +7,10 @@ class EmailDelivered { - /** - * @var \TsfCorp\Email\Models\EmailModel - */ - public $email; - /** - * @var \TsfCorp\Email\Models\EmailRecipient - */ - public $recipient; - /** - * @var mixed - */ - public $payload; + public EmailModel $email; + public EmailRecipient $recipient; + public mixed $payload; - /** - * @param \TsfCorp\Email\Models\EmailModel $email - * @param \TsfCorp\Email\Models\EmailRecipient $recipient - * @param null $payload - */ public function __construct(EmailModel $email, EmailRecipient $recipient, mixed $payload = null) { $this->email = $email; diff --git a/src/Events/EmailFailed.php b/src/Events/EmailFailed.php index e990cfd..3183fd0 100644 --- a/src/Events/EmailFailed.php +++ b/src/Events/EmailFailed.php @@ -7,29 +7,11 @@ class EmailFailed { - /** - * @var \TsfCorp\Email\Models\EmailModel - */ - public $email; - /** - * @var \TsfCorp\Email\Models\EmailRecipient - */ - public $recipient; - /** - * @var string|null - */ - public $reason; - /** - * @var mixed - */ - public $payload; + public EmailModel $email; + public EmailRecipient $recipient; + public ?string $reason; + public mixed $payload; - /** - * @param \TsfCorp\Email\Models\EmailModel $email - * @param \TsfCorp\Email\Models\EmailRecipient $recipient - * @param string|null $reason - * @param null $payload - */ public function __construct(EmailModel $email, EmailRecipient $recipient, ?string $reason = null, mixed $payload = null) { $this->email = $email; diff --git a/src/Events/EmailOpened.php b/src/Events/EmailOpened.php index 24c93ab..5e0f691 100644 --- a/src/Events/EmailOpened.php +++ b/src/Events/EmailOpened.php @@ -7,24 +7,10 @@ class EmailOpened { - /** - * @var \TsfCorp\Email\Models\EmailModel - */ - public $email; - /** - * @var \TsfCorp\Email\Models\EmailRecipient - */ - public $recipient; - /** - * @var mixed - */ - public $payload; + public EmailModel $email; + public EmailRecipient $recipient; + public mixed $payload; - /** - * @param \TsfCorp\Email\Models\EmailModel $email - * @param \TsfCorp\Email\Models\EmailRecipient $recipient - * @param null $payload - */ public function __construct(EmailModel $email, EmailRecipient $recipient, mixed $payload = null) { $this->email = $email; diff --git a/src/Events/EmailSendingFailed.php b/src/Events/EmailSendingFailed.php index fadc973..6f0b6e0 100644 --- a/src/Events/EmailSendingFailed.php +++ b/src/Events/EmailSendingFailed.php @@ -7,19 +7,9 @@ class EmailSendingFailed { - /** - * @var \TsfCorp\Email\Models\EmailModel - */ - public $email; - /** - * @var \Throwable|null - */ - public $exception; + public EmailModel $email; + public ?Throwable $exception; - /** - * @param \TsfCorp\Email\Models\EmailModel $email - * @param \Throwable|null $exception - */ public function __construct(EmailModel $email, ?Throwable $exception = null) { $this->email = $email; diff --git a/src/Events/EmailSendingSucceeded.php b/src/Events/EmailSendingSucceeded.php index 6eb677f..622d7ff 100644 --- a/src/Events/EmailSendingSucceeded.php +++ b/src/Events/EmailSendingSucceeded.php @@ -6,15 +6,8 @@ class EmailSendingSucceeded { - /** - * @var \TsfCorp\Email\Models\EmailModel - */ - public $email; + public EmailModel $email; - /** - * EmailSent constructor. - * @param \TsfCorp\Email\Models\EmailModel $email - */ public function __construct(EmailModel $email) { $this->email = $email; diff --git a/src/Events/EmailUnsubscribed.php b/src/Events/EmailUnsubscribed.php index 96079bb..00ec424 100644 --- a/src/Events/EmailUnsubscribed.php +++ b/src/Events/EmailUnsubscribed.php @@ -7,24 +7,10 @@ class EmailUnsubscribed { - /** - * @var \TsfCorp\Email\Models\EmailModel - */ - public $email; - /** - * @var \TsfCorp\Email\Models\EmailRecipient - */ - public $recipient; - /** - * @var mixed - */ - public $payload; + public EmailModel $email; + public EmailRecipient $recipient; + public mixed $payload; - /** - * @param \TsfCorp\Email\Models\EmailModel $email - * @param \TsfCorp\Email\Models\EmailRecipient $recipient - * @param null $payload - */ public function __construct(EmailModel $email, EmailRecipient $recipient, mixed $payload = null) { $this->email = $email; From 3effbc7e9d36c88b7f5fcbf0c6c1f1f595c66800 Mon Sep 17 00:00:00 2001 From: Ionut Antohi Date: Tue, 20 Jan 2026 15:49:45 +0200 Subject: [PATCH 03/13] remove docs --- .../Controllers/MailgunWebhookController.php | 13 +++--------- src/Http/Controllers/SesWebhookController.php | 20 ++++--------------- src/Http/routes.php | 17 ++++------------ 3 files changed, 11 insertions(+), 39 deletions(-) diff --git a/src/Http/Controllers/MailgunWebhookController.php b/src/Http/Controllers/MailgunWebhookController.php index a4766c4..5e14daf 100644 --- a/src/Http/Controllers/MailgunWebhookController.php +++ b/src/Http/Controllers/MailgunWebhookController.php @@ -3,17 +3,14 @@ namespace TsfCorp\Email\Http\Controllers; use Illuminate\Http\Request; +use Illuminate\Http\Response; use Illuminate\Support\Facades\Validator; use TsfCorp\Email\DefaultWebhookEmailModelResolver; use TsfCorp\Email\Webhooks\Mailgun\MailgunWebhookFactory; class MailgunWebhookController { - /** - * @param Request $request - * @return \Illuminate\Http\Response - */ - public function index(Request $request) + public function index(Request $request): Response { $rules = [ 'event-data.message.headers.message-id' => 'required', @@ -48,11 +45,7 @@ public function index(Request $request) return response('Ok'); } - /** - * @param Request $request - * @return bool - */ - private function checkSignature(Request $request) + private function checkSignature(Request $request): bool { $signature = hash_hmac('SHA256', $request->input('signature.timestamp') . $request->input('signature.token'), config('email.providers.mailgun.webhook_secret')); diff --git a/src/Http/Controllers/SesWebhookController.php b/src/Http/Controllers/SesWebhookController.php index cd5a1d9..62056db 100644 --- a/src/Http/Controllers/SesWebhookController.php +++ b/src/Http/Controllers/SesWebhookController.php @@ -5,18 +5,14 @@ use Aws\Sns\Message; use Aws\Sns\MessageValidator; use Illuminate\Http\Request; +use Illuminate\Http\Response; use Illuminate\Support\Facades\Log; use TsfCorp\Email\DefaultWebhookEmailModelResolver; use TsfCorp\Email\Webhooks\Ses\SesWebhookFactory; class SesWebhookController { - /** - * @param \Illuminate\Http\Request $request - * @param \Aws\Sns\MessageValidator $validator - * @return \Illuminate\Http\Response - */ - public function index(Request $request, MessageValidator $validator) + public function index(Request $request, MessageValidator $validator): Response { $payload = json_decode($request->getContent(), true); @@ -35,22 +31,14 @@ public function index(Request $request, MessageValidator $validator) }; } - /** - * @param $payload - * @return \Illuminate\Http\Response - */ - private function parseSubscriptionConfirmation($payload) + private function parseSubscriptionConfirmation($payload): Response { Log::info("SubscribeURL: " . $payload['SubscribeURL']); return response('Confirmation link received.', 200); } - /** - * @param $payload - * @return \Illuminate\Http\Response - */ - private function parseNotification($payload) + private function parseNotification($payload): Response { /** @var \TsfCorp\Email\WebhookEmailModelResolverInterface $resolver */ $resolver = config('email.webhook_email_model_resolver', DefaultWebhookEmailModelResolver::class); diff --git a/src/Http/routes.php b/src/Http/routes.php index 6957218..d3508a4 100644 --- a/src/Http/routes.php +++ b/src/Http/routes.php @@ -1,17 +1,8 @@ 'TsfCorp\Email\Http\Controllers'], function () { - Route::post('/webhook-mailgun', 'MailgunWebhookController@index'); - Route::post('/webhook-ses', 'SesWebhookController@index'); -}); +Route::post('/webhook-mailgun', [MailgunWebhookController::class, 'index']); +Route::post('/webhook-ses', [SesWebhookController::class, 'index']); From 2fa370648e7995b7c441e65d5ef745ee883f1ff3 Mon Sep 17 00:00:00 2001 From: Ionut Antohi Date: Tue, 20 Jan 2026 15:54:17 +0200 Subject: [PATCH 04/13] format --- src/Jobs/EmailJob.php | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/src/Jobs/EmailJob.php b/src/Jobs/EmailJob.php index c749bb6..0a5adc6 100644 --- a/src/Jobs/EmailJob.php +++ b/src/Jobs/EmailJob.php @@ -18,33 +18,19 @@ class EmailJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - /** - * @var $id - */ - private $id; - /** - * @var \TsfCorp\Email\Models\EmailModel|null - */ - private $email; - - /** - * @param $id - * @param null $database_connection - */ - public function __construct($id, $database_connection = null) + private int $id; + private ?EmailModel $email; + + public function __construct(int $id, ?string $database_connection = null) { $this->id = $id; $this->email = EmailModel::on($database_connection)->find($this->id); } - /** - * - * @throws \Exception - */ public function handle() { if (!$this->email) { - throw new Exception('Record with id [' . $this->id . '] not found.'); + throw new Exception("Record with id [{$this->id}] not found."); } try { @@ -62,9 +48,6 @@ public function handle() $this->sendVia($transport); } - /** - * @param \TsfCorp\Email\Transport $transport - */ public function sendVia(Transport $transport) { if (config('app.env') != 'production') { @@ -79,7 +62,7 @@ public function sendVia(Transport $transport) if ($this->email->retries >= config('email.max_retries')) { $this->email->status = EmailModel::STATUS_FAILED; - $this->email->notes = 'Max retry limit reached. ' . $this->email->notes; + $this->email->notes = "Max retry limit reached. {$this->email->notes}"; $this->email->save(); event(new EmailSendingFailed($this->email)); From 7cc74103fdc721660646182b160afb5b2fc61571 Mon Sep 17 00:00:00 2001 From: Ionut Antohi Date: Tue, 20 Jan 2026 17:59:49 +0200 Subject: [PATCH 05/13] format --- src/Attachment.php | 72 +++++++++------------------------------------- 1 file changed, 13 insertions(+), 59 deletions(-) diff --git a/src/Attachment.php b/src/Attachment.php index 5a8ec17..21d46bf 100644 --- a/src/Attachment.php +++ b/src/Attachment.php @@ -4,44 +4,21 @@ class Attachment implements \JsonSerializable { - /** - * @var null|string - */ - private $path = null; - /** - * @var null|string - */ - private $disk = null; - /** - * @var null|string - */ - private $name = null; + private ?string $disk = null; + private ?string $path = null; + private ?string $name = null; - /** - * @param $path - * @param $name - * @return static - */ - public static function path($path, $name = null) + public static function path(string $path, ?string $name = null): static { return (new static())->setPath($path, $name); } - /** - * @param $disk - * @return static - */ - public static function disk($disk) + public static function disk(string $disk): static { return (new static())->setDisk($disk); } - /** - * @param $path - * @param $name - * @return static - */ - public function setPath($path, $name = null) + public function setPath(string $path, ?string $name = null): static { $this->path = $path; $this->name = $name; @@ -49,56 +26,36 @@ public function setPath($path, $name = null) return $this; } - /** - * @param $disk - * @return static - */ - public function setDisk($disk) + public function setDisk(string $disk): static { $this->disk = $disk; return $this; } - /** - * @param $name - * @return static - */ - public function setName($name) + public function setName(string $name): static { $this->name = $name; return $this; } - /** - * @return string|null - */ - public function getPath() + public function getPath(): ?string { return $this->path; } - /** - * @return string - */ - public function getDisk() + public function getDisk(): string { return $this->disk ?? 'local'; } - /** - * @return string - */ - public function getName() + public function getName(): ?string { return $this->name ?? basename($this->path); } - /** - * @return array - */ - public function toArray() + public function toArray(): array { return [ 'path' => $this->getPath(), @@ -107,10 +64,7 @@ public function toArray() ]; } - /** - * @return array - */ - public function jsonSerialize() + public function jsonSerialize(): array { return $this->toArray(); } From dd0614f6b0e36e7d110552ea02e339a784aa17d8 Mon Sep 17 00:00:00 2001 From: Ionut Antohi Date: Tue, 20 Jan 2026 18:12:28 +0200 Subject: [PATCH 06/13] format --- src/Transport.php | 69 ++++++++++++----------------------------------- 1 file changed, 17 insertions(+), 52 deletions(-) diff --git a/src/Transport.php b/src/Transport.php index 0b7fd80..69f6209 100644 --- a/src/Transport.php +++ b/src/Transport.php @@ -17,57 +17,31 @@ class Transport { - /** - * @var \Symfony\Component\Mailer\Transport\TransportInterface - */ - private $provider; - /** - * @var string|null - */ - private $remote_identifier; - /** - * @var string - */ - private $message; - - /** - * @param \Symfony\Component\Mailer\Transport\TransportInterface $provider - */ + private TransportInterface $provider; + private ?string $remote_identifier; + private ?string $message; + public function __construct(TransportInterface $provider) { $this->provider = $provider; } - /** - * @return \Symfony\Component\Mailer\Transport\TransportInterface - */ - public function getProvider() + public function getProvider(): TransportInterface { return $this->provider; } - /** - * @return string|null - */ - public function getRemoteIdentifier() + public function getRemoteIdentifier(): ?string { return $this->remote_identifier; } - /** - * @return string - */ - public function getMessage() + public function getMessage(): ?string { return $this->message; } - /** - * @param \TsfCorp\Email\Models\EmailModel $email - * @throws \Symfony\Component\Mailer\Exception\TransportExceptionInterface - * @throws \Throwable - */ - public function send(EmailModel $email) + public function send(EmailModel $email): void { try { $from = $this->fromJson($email->from); @@ -77,20 +51,20 @@ public function send(EmailModel $email) $bcc = $email->bcc->map(fn(EmailRecipient $r) => $r->asMimeAddress()); $reply_to = array_map(function ($recipient) { - return new Address($recipient->email, $recipient->name ?? ''); + return new Address($recipient['email'], $recipient['name'] ?? ''); }, $this->fromJson($email->reply_to)); $attachments = array_map(function ($attachment) { return (new Attachment()) - ->setPath($attachment->path) - ->setDisk($attachment->disk) - ->setName($attachment->name ?? null); + ->setPath($attachment['path']) + ->setDisk($attachment['disk']) + ->setName($attachment['name'] ?? null); }, $this->fromJson($email->attachments)); - $metadata = (array)$this->fromJson($email->metadata); + $metadata = $this->fromJson($email->metadata); $symfony_email = (new \Symfony\Component\Mime\Email()) - ->from(new Address($from->email, $from->name ?? '')) + ->from(new Address($from['email'], $from['name'] ?? '')) ->to(...$to) ->cc(...$cc) ->bcc(...$bcc) @@ -126,12 +100,7 @@ public function send(EmailModel $email) } } - /** - * @param \TsfCorp\Email\Models\EmailModel $email - * @return \TsfCorp\Email\Transport - * @throws \Exception - */ - public static function resolveFor(EmailModel $email) + public static function resolveFor(EmailModel $email): static { $provider = null; @@ -160,13 +129,9 @@ public static function resolveFor(EmailModel $email) return new static($provider); } - /** - * @param $json - * @return array - */ - private function fromJson($json) + private function fromJson($json): array { - $decoded = json_decode($json); + $decoded = json_decode($json, true); if (json_last_error() !== JSON_ERROR_NONE) { $decoded = []; From 4d3dea70260f26690c07683b8f6e70dbfd926976 Mon Sep 17 00:00:00 2001 From: Ionut Antohi Date: Wed, 21 Jan 2026 06:07:57 +0200 Subject: [PATCH 07/13] format --- src/Email.php | 282 +++++++++++--------------------------------------- 1 file changed, 59 insertions(+), 223 deletions(-) diff --git a/src/Email.php b/src/Email.php index 7ca66f9..f7b9a3d 100644 --- a/src/Email.php +++ b/src/Email.php @@ -5,80 +5,33 @@ use Carbon\Carbon; use Exception; use Illuminate\Support\Str; +use Illuminate\View\View; use TsfCorp\Email\Models\EmailModel; use TsfCorp\Email\Models\EmailRecipient; class Email { - /** - * @var string - */ - private $project; - /** - * @var string - */ - private $uuid; - /** - * @var string - */ - private $provider; - /** - * @var array - */ - private $from = []; - /** - * @var array - */ - private $recipients = []; - /** - * @var array - */ - private $reply_to = []; - /** - * @var string - */ - private $subject; - /** - * @var string - */ - private $body; - /** - * @var array - */ - private $attachments = []; - /** - * @var array - */ - private $metadata = []; - /** - * @var array - */ - private $available_providers = ['mailgun', 'ses', 'google-smtp']; - /** - * @var \TsfCorp\Email\Models\EmailModel|null - */ - private $model; - /** - * @var string - */ - private $database_connection = null; + private string $provider; + private array $from = []; + private array $recipients = []; + private array $reply_to = []; + private string $subject = ''; + private mixed $body = null; + private array $attachments = []; + private array $metadata = []; + private array $available_providers = ['mailgun', 'ses', 'google-smtp']; + private ?EmailModel $model = null; + private ?string $database_connection = null; public function __construct() { - $this->project = config('email.project'); $this->provider = config('email.default_provider'); - $this->uuid = Str::uuid(); } - /** - * @param $provider - * @return static - * @throws \Exception - */ - public function via($provider) + public function via(string $provider): static { if (!in_array($provider, $this->available_providers)) { - throw new Exception('Unrecognized email provider [' . $provider . ']'); + throw new Exception("Unrecognized email provider [{$provider}]"); } $this->provider = $provider; @@ -86,255 +39,151 @@ public function via($provider) return $this; } - /** - * @param $name - * @return static - */ - public function setDatabaseConnection($name) + public function setDatabaseConnection(string $name): static { $this->database_connection = $name; return $this; } - /** - * @param $type - * @param $email - * @param $name - * @return static - * @throws \Exception - */ - public function addRecipient($type, $email, $name = null) + public function from(?string $from, ?string $name = null): static { - if (!$this->isValidEmailAddress($email)) { - throw new Exception("Invalid {$type} address: {$email}"); + if (!$this->isValidEmailAddress($from)) { + throw new Exception("Invalid from address: {$from}"); } - $this->recipients[] = [ - 'type' => $type, - 'email' => $email, + $this->from = [ + 'email' => $from, 'name' => $name, ]; return $this; } - /** - * @param $from - * @param null $name - * @return static - * @throws \Exception - */ - public function from($from, $name = null) + public function replyTo(?string $reply_to, ?string $name = null): static { - if (!$this->isValidEmailAddress($from)) { - throw new Exception("Invalid from address: {$from}"); + if (!$this->isValidEmailAddress($reply_to)) { + throw new Exception("Invalid reply to address: {$reply_to}"); } - $this->from = [ - 'email' => $from, + $this->reply_to[] = [ + 'email' => $reply_to, 'name' => $name, ]; return $this; } - /** - * @param $to - * @param null $name - * @return static - * @throws \Exception - */ - public function to($to, $name = null) + public function addRecipient(string $type, string $email, ?string $name = null): static { - $this->addRecipient(EmailRecipient::TYPE_TO, $to, $name); + if (!$this->isValidEmailAddress($email)) { + throw new Exception("Invalid {$type} address: {$email}"); + } + + $this->recipients[] = [ + 'type' => $type, + 'email' => $email, + 'name' => $name, + ]; return $this; } - /** - * @param $cc - * @param null $name - * @return static - * @throws \Exception - */ - public function cc($cc, $name = null) + public function to(string $to, ?string $name = null): static { - $this->addRecipient(EmailRecipient::TYPE_CC, $cc, $name); + $this->addRecipient(EmailRecipient::TYPE_TO, $to, $name); return $this; } - /** - * @param $bcc - * @param null $name - * @return $this - * @throws \Exception - */ - public function bcc($bcc, $name = null) + public function cc(string $cc, ?string $name = null): static { - $this->addRecipient(EmailRecipient::TYPE_BCC, $bcc, $name); + $this->addRecipient(EmailRecipient::TYPE_CC, $cc, $name); return $this; } - /** - * @param $reply_to - * @param null $name - * @return static - * @throws \Exception - */ - public function replyTo($reply_to, $name = null) + public function bcc(string $bcc, ?string $name = null): static { - if (!$this->isValidEmailAddress($reply_to)) { - throw new Exception("Invalid reply to address: {$reply_to}"); - } - - $this->reply_to[] = [ - 'email' => $reply_to, - 'name' => $name, - ]; + $this->addRecipient(EmailRecipient::TYPE_BCC, $bcc, $name); return $this; } - /** - * @param $subject - * @return static - */ - public function subject($subject) + public function subject(string $subject): static { $this->subject = $subject; return $this; } - /** - * @param $body - * @return static - */ - public function body($body) + public function body(mixed $body): static { $this->body = $body; return $this; } - /** - * @param \TsfCorp\Email\Attachment $attachment - * @return static - */ - public function addAttachment(Attachment $attachment) + public function addAttachment(Attachment $attachment): static { $this->attachments[] = $attachment; return $this; } - /** - * @param \TsfCorp\Email\Attachment $attachment - * @return static - */ - public function addMetadata($key, $value) + public function addMetadata(string $key, mixed $value): static { $this->metadata[$key] = $value; return $this; } - /** - * @return array - */ - public function getRecipients() + public function getRecipients(): array { return $this->recipients; } - /** - * @return array - */ - public function getTo() + public function getTo(): array { return array_filter($this->recipients, fn($recipient) => $recipient['type'] === EmailRecipient::TYPE_TO); } - /** - * @return array - */ - public function getCc() + public function getCc(): array { return array_filter($this->recipients, fn($recipient) => $recipient['type'] === EmailRecipient::TYPE_CC); } - /** - * @return array - */ - public function getBcc() + public function getBcc(): array { return array_filter($this->recipients, fn($recipient) => $recipient['type'] === EmailRecipient::TYPE_BCC); } - /** - * @return \TsfCorp\Email\Models\EmailModel|null - */ - public function getModel() + public function getModel(): ?EmailModel { return $this->model; } - /** - * @return array|string[] - */ - public function getAvailableProviders() - { - return $this->available_providers; - } - - /** - * @return string - */ - public function getUuid() - { - return $this->uuid; - } - - /** - * @return string - */ - public function render() - { - return $this->body; - } - - /** - * Saves new email in database - * - * @return static - * @throws \Exception - */ - public function enqueue() + public function enqueue(): static { if (!count($this->from)) { $this->from(config('email.from.address'), config('email.from.name')); } - $to = array_filter($this->recipients, fn($recipient) => $recipient['type'] === EmailRecipient::TYPE_TO); - - if (!count($to)) { + if (!count($this->getTo())) { throw new Exception('Missing to address.'); } $this->model = new EmailModel; $this->model->setConnection($this->database_connection); - $this->model->project = $this->project; - $this->model->uuid = $this->uuid; + $this->model->uuid = Str::uuid(); + $this->model->project = config('email.project'); $this->model->from = json_encode($this->from); $this->model->reply_to = count($this->reply_to) ? json_encode($this->reply_to) : null; - $this->model->subject = $this->subject; - $this->model->body = $this->body; $this->model->attachments = count($this->attachments) ? json_encode($this->attachments) : null; $this->model->metadata = count($this->metadata) ? json_encode($this->metadata) : null; + $this->model->subject = $this->subject; + $this->model->body = $this->body; $this->model->provider = $this->provider; $this->model->status = EmailModel::STATUS_PENDING; $this->model->save(); @@ -351,12 +200,7 @@ public function enqueue() return $this; } - /** - * Dispatches a job which will send the email - * - * @throws \Exception - */ - public function dispatch(?Carbon $delay = null) + public function dispatch(?Carbon $delay = null): static { if (!$this->model) { throw new Exception('There is no email to be dispatched.'); @@ -367,20 +211,12 @@ public function dispatch(?Carbon $delay = null) return $this; } - /** - * @return static - * @throws \Exception - */ - public function send(?Carbon $delay = null) + public function send(?Carbon $delay = null): static { return $this->enqueue()->dispatch($delay); } - /** - * @param $email_address - * @return bool - */ - private function isValidEmailAddress($email_address) + private function isValidEmailAddress(?string $email_address): bool { return !empty($email_address) && filter_var($email_address, FILTER_VALIDATE_EMAIL); } From 19fa0655ad282052a3829b3e8385e68ee5de5148 Mon Sep 17 00:00:00 2001 From: Ionut Antohi Date: Wed, 21 Jan 2026 06:18:44 +0200 Subject: [PATCH 08/13] format --- src/EmailServiceProvider.php | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/EmailServiceProvider.php b/src/EmailServiceProvider.php index 1cc278b..5a3d7f6 100644 --- a/src/EmailServiceProvider.php +++ b/src/EmailServiceProvider.php @@ -8,19 +8,7 @@ class EmailServiceProvider extends ServiceProvider { - /** - * Indicates if loading of the provider is deferred. - * - * @var bool - */ - protected $defer = false; - - /** - * Perform post-registration booting of services. - * - * @return void - */ - public function boot() + public function boot(): void { if (!$this->app->routesAreCached()) { require __DIR__ . '/Http/routes.php'; @@ -41,17 +29,4 @@ public function boot() ]); } } - - /** - * Added for L.5.1 compatibility - */ - public function register() - { - - } - - public function provides() - { - return ['email']; - } } From 4e90476736d68c31c785c0092c04af44a0aeec82 Mon Sep 17 00:00:00 2001 From: Ionut Antohi Date: Wed, 21 Jan 2026 06:21:56 +0200 Subject: [PATCH 09/13] format --- src/Attachment.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Attachment.php b/src/Attachment.php index 21d46bf..9af03f5 100644 --- a/src/Attachment.php +++ b/src/Attachment.php @@ -2,7 +2,9 @@ namespace TsfCorp\Email; -class Attachment implements \JsonSerializable +use JsonSerializable; + +class Attachment implements JsonSerializable { private ?string $disk = null; private ?string $path = null; From 26fc6fede5bf94bf78c18214f8deab9270fb5a33 Mon Sep 17 00:00:00 2001 From: Ionut Antohi Date: Wed, 21 Jan 2026 06:27:20 +0200 Subject: [PATCH 10/13] format --- src/Transport.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Transport.php b/src/Transport.php index 69f6209..fcce618 100644 --- a/src/Transport.php +++ b/src/Transport.php @@ -129,9 +129,9 @@ public static function resolveFor(EmailModel $email): static return new static($provider); } - private function fromJson($json): array + private function fromJson(?string $json): array { - $decoded = json_decode($json, true); + $decoded = json_decode((string)$json, true); if (json_last_error() !== JSON_ERROR_NONE) { $decoded = []; From 1125bbe5496879532f4da29751a610948345d091 Mon Sep 17 00:00:00 2001 From: Ionut Antohi Date: Wed, 21 Jan 2026 08:13:37 +0200 Subject: [PATCH 11/13] format --- src/Models/EmailModel.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Models/EmailModel.php b/src/Models/EmailModel.php index 00c2338..a073bf5 100644 --- a/src/Models/EmailModel.php +++ b/src/Models/EmailModel.php @@ -104,7 +104,6 @@ public function getMetadata() } /** - * Dispatches a job for current record * @param \Carbon\Carbon|null $delay */ public function dispatchJob(?Carbon $delay = null) From 29c5306828d613c27df980779ad302b82742f972 Mon Sep 17 00:00:00 2001 From: Ionut Antohi Date: Wed, 21 Jan 2026 08:40:59 +0200 Subject: [PATCH 12/13] refactor attachment --- src/Attachment.php | 29 +++++++++-------------------- src/Transport.php | 9 +++++---- 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/Attachment.php b/src/Attachment.php index 9af03f5..d37515f 100644 --- a/src/Attachment.php +++ b/src/Attachment.php @@ -6,23 +6,19 @@ class Attachment implements JsonSerializable { - private ?string $disk = null; - private ?string $path = null; - private ?string $name = null; + private string $path; + private ?string $name; + private string $disk; - public static function path(string $path, ?string $name = null): static + public function __construct(string $path, ?string $name = null, ?string $disk = 'local') { - return (new static())->setPath($path, $name); - } - - public static function disk(string $disk): static - { - return (new static())->setDisk($disk); + $this->path = $path; + $this->name = $name; + $this->disk = $disk; } - public function setPath(string $path, ?string $name = null): static + public function setName(string $name): static { - $this->path = $path; $this->name = $name; return $this; @@ -35,13 +31,6 @@ public function setDisk(string $disk): static return $this; } - public function setName(string $name): static - { - $this->name = $name; - - return $this; - } - public function getPath(): ?string { return $this->path; @@ -52,7 +41,7 @@ public function getDisk(): string return $this->disk ?? 'local'; } - public function getName(): ?string + public function getName(): string { return $this->name ?? basename($this->path); } diff --git a/src/Transport.php b/src/Transport.php index fcce618..81153ff 100644 --- a/src/Transport.php +++ b/src/Transport.php @@ -55,10 +55,11 @@ public function send(EmailModel $email): void }, $this->fromJson($email->reply_to)); $attachments = array_map(function ($attachment) { - return (new Attachment()) - ->setPath($attachment['path']) - ->setDisk($attachment['disk']) - ->setName($attachment['name'] ?? null); + return new Attachment( + path: $attachment['path'], + name: $attachment['name'], + disk: $attachment['disk'], + ); }, $this->fromJson($email->attachments)); $metadata = $this->fromJson($email->metadata); From 03758a29dfcf942bc9dc8083695b054cfe7abf0c Mon Sep 17 00:00:00 2001 From: Ionut Antohi Date: Wed, 21 Jan 2026 08:51:39 +0200 Subject: [PATCH 13/13] fix text --- tests/EmailCreationTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/EmailCreationTest.php b/tests/EmailCreationTest.php index e57903f..14c4b7c 100644 --- a/tests/EmailCreationTest.php +++ b/tests/EmailCreationTest.php @@ -55,9 +55,9 @@ public function test_email_is_saved_in_database() ->replyTo('reply_to@mail.com', 'Reply to name') ->subject('Subject') ->body('Body') - ->addAttachment(Attachment::path('attachment_1.txt')) - ->addAttachment(Attachment::path('attachment_2.txt', 'custom_name_2.txt')) - ->addAttachment(Attachment::disk('s3')->setPath('attachment_3.txt', 'custom_name_3.txt')) + ->addAttachment(new Attachment('attachment_1.txt')) + ->addAttachment(new Attachment('attachment_2.txt', 'custom_name_2.txt')) + ->addAttachment(new Attachment('attachment_3.txt', 'custom_name_3.txt', 's3')) ->addMetadata('key_1', 'value_1') ->addMetadata('key_2', 'value_2') ->via('mailgun')