From 6f8a387f078b30837ec2f6d43402e8aeb0e4b93a Mon Sep 17 00:00:00 2001
From: Eduard Lupacescu <eduard.lupacescu@binarcode.com>
Date: Fri, 3 Sep 2021 13:32:13 +0300
Subject: [PATCH 01/34] wip

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 57522eb5b19d..8d32d091ae93 100644
--- a/README.md
+++ b/README.md
@@ -335,7 +335,7 @@ Mailator has few events you can use.
 
 If your mailable class extends the `Binarcode\LaravelMailator\Contracts\Beforable`, you will be able to inject the `before` method, that will be called right before the sending the email. 
 
-If your mailable class extends the `Binarcode\LaravelMailator\Contracts\Afterable`, you will be able to inject the `before` method, that will be called right after the mail has being sent.
+If your mailable class extends the `Binarcode\LaravelMailator\Contracts\Afterable`, you will be able to inject the `after` method, that will be called right after the mail has being sent.
 
 
 And latest, after each mail has being sent, mailator will fire the `Binarcode\LaravelMailator\Events\ScheduleMailSentEvent` event, so you can listen for it.

From 5ca6ab735bee4e21b3a900b56958c3096eee30ea Mon Sep 17 00:00:00 2001
From: Eduard Lupacescu <eduard.lupacescu@binarcode.com>
Date: Wed, 9 Feb 2022 15:46:23 +0200
Subject: [PATCH 02/34] fix: laravel 9 support

---
 .github/workflows/run-tests.yml | 2 +-
 composer.json                   | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index cd021030dd85..4078d89992e6 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -10,7 +10,7 @@ jobs:
       matrix:
         os: [ubuntu-latest]
         php: [8.0]
-        laravel: [^8.0]
+        laravel: [^8.0, ^9.0]
         dependency-version: [prefer-stable]
         include:
           - laravel: ^8.0
diff --git a/composer.json b/composer.json
index c8ab9bc6af5f..1f74cc392245 100644
--- a/composer.json
+++ b/composer.json
@@ -18,13 +18,13 @@
     ],
     "require": {
         "php": "^8.0",
-        "illuminate/contracts": "^8.37",
+        "illuminate/contracts": "^8.37|^9.0",
         "opis/closure": "^3.6"
     },
     "require-dev": {
         "brianium/paratest": "^6.2",
         "nunomaduro/collision": "^5.3",
-        "orchestra/testbench": "^6.15",
+        "orchestra/testbench": "^6.15|^7.0",
         "phpunit/phpunit": "^9.3",
         "spatie/laravel-ray": "^1.9",
         "spatie/test-time": "^1.2",

From 65db6721e66d82ff5b6877f19c616bc9a047ec9d Mon Sep 17 00:00:00 2001
From: Eduard Lupacescu <eduard.lupacescu@binarcode.com>
Date: Wed, 9 Feb 2022 15:53:10 +0200
Subject: [PATCH 03/34] fix: wip

---
 .github/workflows/run-tests.yml | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index 4078d89992e6..6064b5f0ae19 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -12,9 +12,6 @@ jobs:
         php: [8.0]
         laravel: [^8.0, ^9.0]
         dependency-version: [prefer-stable]
-        include:
-          - laravel: ^8.0
-            testbench: ^6.0
 
     name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} - ${{ matrix.os }}
 

From 7a50d12de5c9324eb559f3086ef903017e1c1ba0 Mon Sep 17 00:00:00 2001
From: Eduard Lupacescu <eduard.lupacescu@binarcode.com>
Date: Wed, 9 Feb 2022 15:54:38 +0200
Subject: [PATCH 04/34] fix: wip

---
 composer.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/composer.json b/composer.json
index 1f74cc392245..8653489764b6 100644
--- a/composer.json
+++ b/composer.json
@@ -24,7 +24,7 @@
     "require-dev": {
         "brianium/paratest": "^6.2",
         "nunomaduro/collision": "^5.3",
-        "orchestra/testbench": "^6.15|^7.0",
+        "orchestra/testbench": "^6.0|^7.0",
         "phpunit/phpunit": "^9.3",
         "spatie/laravel-ray": "^1.9",
         "spatie/test-time": "^1.2",

From 642ab5ab3684775ba9eacd9c1db5fc47eb15dd2f Mon Sep 17 00:00:00 2001
From: Eduard Lupacescu <eduard.lupacescu@binarcode.com>
Date: Wed, 9 Feb 2022 15:57:01 +0200
Subject: [PATCH 05/34] fix: wip

---
 composer.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/composer.json b/composer.json
index 8653489764b6..492e6a05a6d7 100644
--- a/composer.json
+++ b/composer.json
@@ -24,7 +24,7 @@
     "require-dev": {
         "brianium/paratest": "^6.2",
         "nunomaduro/collision": "^5.3",
-        "orchestra/testbench": "^6.0|^7.0",
+        "orchestra/testbench": "^6.0 || ^7.0",
         "phpunit/phpunit": "^9.3",
         "spatie/laravel-ray": "^1.9",
         "spatie/test-time": "^1.2",

From 86b4032250609230618268472f40081ab4717aa9 Mon Sep 17 00:00:00 2001
From: Eduard Lupacescu <eduard.lupacescu@binarcode.com>
Date: Wed, 9 Feb 2022 16:01:53 +0200
Subject: [PATCH 06/34] fix: wip

---
 composer.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/composer.json b/composer.json
index 492e6a05a6d7..a7b9f408ec67 100644
--- a/composer.json
+++ b/composer.json
@@ -24,7 +24,7 @@
     "require-dev": {
         "brianium/paratest": "^6.2",
         "nunomaduro/collision": "^5.3",
-        "orchestra/testbench": "^6.0 || ^7.0",
+        "orchestra/testbench": "^6.0",
         "phpunit/phpunit": "^9.3",
         "spatie/laravel-ray": "^1.9",
         "spatie/test-time": "^1.2",

From 4bc16fc4851170b550ead9d19a95e7a0d62f25ea Mon Sep 17 00:00:00 2001
From: Eduard Lupacescu <eduard.lupacescu@binarcode.com>
Date: Wed, 9 Feb 2022 16:10:18 +0200
Subject: [PATCH 07/34] fix: wip

---
 .github/workflows/run-tests.yml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index 6064b5f0ae19..ea2b2af0dd4b 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -12,6 +12,9 @@ jobs:
         php: [8.0]
         laravel: [^8.0, ^9.0]
         dependency-version: [prefer-stable]
+        include:
+            -   laravel: 8.*
+                testbench: ^6.23
 
     name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} - ${{ matrix.os }}
 

From c4c2dc0933ebefc8b078d71b4f6f43750579ac9a Mon Sep 17 00:00:00 2001
From: Eduard Lupacescu <eduard.lupacescu@binarcode.com>
Date: Wed, 9 Feb 2022 16:11:30 +0200
Subject: [PATCH 08/34] fix: wip

---
 .github/workflows/run-tests.yml | 4 +++-
 composer.json                   | 2 +-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index ea2b2af0dd4b..5908540f0459 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -14,7 +14,9 @@ jobs:
         dependency-version: [prefer-stable]
         include:
             -   laravel: 8.*
-                testbench: ^6.23
+                testbench: ^6.0
+            -   laravel: 9.*
+                testbench: ^7.0
 
     name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} - ${{ matrix.os }}
 
diff --git a/composer.json b/composer.json
index a7b9f408ec67..310b8f009ea5 100644
--- a/composer.json
+++ b/composer.json
@@ -24,7 +24,7 @@
     "require-dev": {
         "brianium/paratest": "^6.2",
         "nunomaduro/collision": "^5.3",
-        "orchestra/testbench": "^6.0",
+        "orchestra/testbench": "^6.0 | ^7.0",
         "phpunit/phpunit": "^9.3",
         "spatie/laravel-ray": "^1.9",
         "spatie/test-time": "^1.2",

From 4384ee753a8b0a7e84baee035b1d29362117cbc3 Mon Sep 17 00:00:00 2001
From: Eduard Lupacescu <eduard.lupacescu@binarcode.com>
Date: Wed, 9 Feb 2022 16:13:01 +0200
Subject: [PATCH 09/34] fix: wip

---
 src/Models/MailatorSchedule.php | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/Models/MailatorSchedule.php b/src/Models/MailatorSchedule.php
index 6fa7ac509496..176132b5fbb9 100644
--- a/src/Models/MailatorSchedule.php
+++ b/src/Models/MailatorSchedule.php
@@ -53,6 +53,7 @@
  * @property Carbon $completed_at
  * @property string $frequency_option
  * @property-read Collection $logs
+ *
  * @method static MailatorSchedulerBuilder query()
  */
 class MailatorSchedule extends Model

From 55dbf0ac6e9f586f4262cbc9cc260026fe4fd638 Mon Sep 17 00:00:00 2001
From: Eduard Lupacescu <eduard.lupacescu@binarcode.com>
Date: Wed, 9 Feb 2022 16:17:33 +0200
Subject: [PATCH 10/34] fix: wip

---
 .github/workflows/run-tests.yml | 81 ++++++++++++++++-----------------
 composer.json                   |  2 +-
 2 files changed, 40 insertions(+), 43 deletions(-)

diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index 5908540f0459..7cf5df09d165 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -1,46 +1,43 @@
-name: Tests
+name: run-tests
 
 on: [push, pull_request]
 
 jobs:
-  test:
-    runs-on: ${{ matrix.os }}
-    strategy:
-      fail-fast: true
-      matrix:
-        os: [ubuntu-latest]
-        php: [8.0]
-        laravel: [^8.0, ^9.0]
-        dependency-version: [prefer-stable]
-        include:
-            -   laravel: 8.*
-                testbench: ^6.0
-            -   laravel: 9.*
-                testbench: ^7.0
-
-    name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} - ${{ matrix.os }}
-
-    steps:
-      - name: Checkout code
-        uses: actions/checkout@v2
-
-      - name: Cache dependencies
-        uses: actions/cache@v2
-        with:
-          path: ~/.composer/cache/files
-          key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
-
-      - name: Setup PHP
-        uses: shivammathur/setup-php@v2
-        with:
-          php-version: ${{ matrix.php }}
-          extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick
-          coverage: none
-
-      - name: Install dependencies
-        run: |
-          composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
-          composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest
-
-      - name: Execute tests
-        run: composer test
+    test:
+        runs-on: ${{ matrix.os }}
+        strategy:
+            fail-fast: true
+            matrix:
+                os: [ubuntu-latest, windows-latest]
+                php: [8.1, 8.0]
+                laravel: [^8.0, ^9.0]
+                stability: [prefer-stable]
+                include:
+                    - laravel: 8.*
+                      testbench: ^6.6
+
+        name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
+
+        steps:
+            - name: Checkout code
+              uses: actions/checkout@v2
+
+            - name: Setup PHP
+              uses: shivammathur/setup-php@v2
+              with:
+                  php-version: ${{ matrix.php }}
+                  extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
+                  coverage: none
+
+            - name: Setup problem matchers
+              run: |
+                  echo "::add-matcher::${{ runner.tool_cache }}/php.json"
+                  echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
+
+            - name: Install dependencies
+              run: |
+                  composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
+                  composer update --${{ matrix.stability }} --prefer-dist --no-interaction
+
+            - name: Execute tests
+              run: ./vendor/bin/testbench package:test --parallel --no-coverage
diff --git a/composer.json b/composer.json
index 310b8f009ea5..8653489764b6 100644
--- a/composer.json
+++ b/composer.json
@@ -24,7 +24,7 @@
     "require-dev": {
         "brianium/paratest": "^6.2",
         "nunomaduro/collision": "^5.3",
-        "orchestra/testbench": "^6.0 | ^7.0",
+        "orchestra/testbench": "^6.0|^7.0",
         "phpunit/phpunit": "^9.3",
         "spatie/laravel-ray": "^1.9",
         "spatie/test-time": "^1.2",

From 9153d09e0c36e10e5aa0171370c25479332c68b0 Mon Sep 17 00:00:00 2001
From: Eduard Lupacescu <eduard.lupacescu@binarcode.com>
Date: Wed, 9 Feb 2022 16:35:07 +0200
Subject: [PATCH 11/34] fix: wip

---
 composer.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/composer.json b/composer.json
index 8653489764b6..9e6c1324236f 100644
--- a/composer.json
+++ b/composer.json
@@ -18,7 +18,7 @@
     ],
     "require": {
         "php": "^8.0",
-        "illuminate/contracts": "^8.37|^9.0",
+        "illuminate/contracts": "^8.0|^9.0",
         "opis/closure": "^3.6"
     },
     "require-dev": {

From 7934ab2bd370efe27ada29ab559fb203ca1206d8 Mon Sep 17 00:00:00 2001
From: Lupacescu Eduard <eduard.lupacescu@binarcode.com>
Date: Thu, 10 Feb 2022 11:39:32 +0200
Subject: [PATCH 12/34] fix: restrict to laravel 9 (#2559)

* fix: restrict to laravel 9

* fix: upgrade

* fix: wip

* fix: wip

* fix: wip

* fix: wip

* fix: phpstan

* fix: wip

* fix: wip

* fix: wip

* fix: wip
---
 .github/workflows/phpstan.yml                 | 26 +++++++++++++++
 .github/workflows/psalm.yml                   | 33 -------------------
 .github/workflows/run-tests.yml               |  8 ++---
 .github/workflows/update-changelog.yml        | 28 ++++++++++++++++
 Upgrade.md                                    |  5 +++
 composer.json                                 | 20 ++++++-----
 phpstan-baseline.neon                         | 18 ++++++++++
 phpstan.neon.dist                             | 12 +++++++
 psalm.xml.dist                                | 16 ---------
 src/Constraints/AfterConstraint.php           | 18 +++++-----
 src/Constraints/BeforeConstraint.php          |  6 ++--
 .../Builders/MailatorSchedulerBuilder.php     |  3 ++
 src/Models/Concerns/ConstraintsResolver.php   |  1 +
 src/Models/Concerns/WithUuid.php              |  5 ++-
 src/Models/MailTemplate.php                   | 24 ++++++++------
 src/Models/MailTemplatePlaceholder.php        | 10 +++---
 src/Models/MailatorLog.php                    |  7 +++-
 src/Models/MailatorSchedule.php               |  4 ++-
 .../Concerns/ReplaceModelAttributes.php       |  2 +-
 tests/Feature/Models/MailatorScheduleTest.php |  6 ++--
 20 files changed, 158 insertions(+), 94 deletions(-)
 create mode 100644 .github/workflows/phpstan.yml
 delete mode 100644 .github/workflows/psalm.yml
 create mode 100644 .github/workflows/update-changelog.yml
 create mode 100644 Upgrade.md
 create mode 100644 phpstan-baseline.neon
 create mode 100644 phpstan.neon.dist
 delete mode 100644 psalm.xml.dist

diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml
new file mode 100644
index 000000000000..5559cee274a4
--- /dev/null
+++ b/.github/workflows/phpstan.yml
@@ -0,0 +1,26 @@
+name: PHPStan
+
+on:
+    push:
+        paths:
+            - '**.php'
+            - 'phpstan.neon.dist'
+
+jobs:
+    phpstan:
+        name: phpstan
+        runs-on: ubuntu-latest
+        steps:
+            - uses: actions/checkout@v2
+
+            - name: Setup PHP
+              uses: shivammathur/setup-php@v2
+              with:
+                  php-version: '8.0'
+                  coverage: none
+
+            - name: Install composer dependencies
+              uses: ramsey/composer-install@v1
+
+            - name: Run PHPStan
+              run: ./vendor/bin/phpstan --error-format=github
diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml
deleted file mode 100644
index e48b24f547a2..000000000000
--- a/.github/workflows/psalm.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-name: Psalm
-
-on:
-  push:
-    paths:
-      - '**.php'
-      - 'psalm.xml.dist'
-
-jobs:
-  psalm:
-    name: psalm
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-
-      - name: Setup PHP
-        uses: shivammathur/setup-php@v2
-        with:
-          php-version: '8.0'
-          extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick
-          coverage: none
-
-      - name: Cache composer dependencies
-        uses: actions/cache@v2
-        with:
-          path: vendor
-          key: composer-${{ hashFiles('composer.lock') }}
-
-      - name: Run composer install
-        run: composer install -n --prefer-dist
-
-      - name: Run psalm
-        run: ./vendor/bin/psalm --output-format=github
diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index 7cf5df09d165..bf7e0e2523c6 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -9,12 +9,12 @@ jobs:
             fail-fast: true
             matrix:
                 os: [ubuntu-latest, windows-latest]
-                php: [8.1, 8.0]
-                laravel: [^8.0, ^9.0]
+                php: [8.0, 8.1]
+                laravel: [^9.0]
                 stability: [prefer-stable]
                 include:
-                    - laravel: 8.*
-                      testbench: ^6.6
+                    - laravel: ^9.0
+                      testbench: ^7.0
 
         name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
 
diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml
new file mode 100644
index 000000000000..6c58564a807f
--- /dev/null
+++ b/.github/workflows/update-changelog.yml
@@ -0,0 +1,28 @@
+name: "Update Changelog"
+
+on:
+    release:
+        types: [released]
+
+jobs:
+    update:
+        runs-on: ubuntu-latest
+
+        steps:
+            - name: Checkout code
+              uses: actions/checkout@v2
+              with:
+                  ref: main
+
+            - name: Update Changelog
+              uses: stefanzweifel/changelog-updater-action@v1
+              with:
+                  latest-version: ${{ github.event.release.name }}
+                  release-notes: ${{ github.event.release.body }}
+
+            - name: Commit updated CHANGELOG
+              uses: stefanzweifel/git-auto-commit-action@v4
+              with:
+                  branch: main
+                  commit_message: Update CHANGELOG
+                  file_pattern: CHANGELOG.md
diff --git a/Upgrade.md b/Upgrade.md
new file mode 100644
index 000000000000..b8eee889d96a
--- /dev/null
+++ b/Upgrade.md
@@ -0,0 +1,5 @@
+### From v3 to v4
+
+Upgrading from v3 to v4 is straightforward. The biggest change is that we dropped support for Laravel bellow than 9, and are using PHP 8 features.
+
+
diff --git a/composer.json b/composer.json
index 9e6c1324236f..5e8eabb94bf0 100644
--- a/composer.json
+++ b/composer.json
@@ -18,17 +18,18 @@
     ],
     "require": {
         "php": "^8.0",
-        "illuminate/contracts": "^8.0|^9.0",
+        "illuminate/support": "^9.0",
         "opis/closure": "^3.6"
     },
     "require-dev": {
         "brianium/paratest": "^6.2",
-        "nunomaduro/collision": "^5.3",
-        "orchestra/testbench": "^6.0|^7.0",
-        "phpunit/phpunit": "^9.3",
+        "nunomaduro/collision": "^6.0",
+        "nunomaduro/larastan": "^2.0",
+        "orchestra/testbench": "^7.0",
+        "phpstan/extension-installer": "^1.1",
+        "phpunit/phpunit": "^9.5",
         "spatie/laravel-ray": "^1.9",
-        "spatie/test-time": "^1.2",
-        "vimeo/psalm": "^4.4"
+        "spatie/test-time": "^1.2"
     },
     "autoload": {
         "psr-4": {
@@ -41,12 +42,15 @@
         }
     },
     "scripts": {
-        "psalm": "vendor/bin/psalm",
+        "analyse": "vendor/bin/phpstan analyse",
         "test": "./vendor/bin/testbench package:test --parallel --no-coverage",
         "test-coverage": "vendor/bin/phpunit --coverage-html coverage"
     },
     "config": {
-        "sort-packages": true
+        "sort-packages": true,
+        "allow-plugins": {
+            "phpstan/extension-installer": true
+        }
     },
     "extra": {
         "laravel": {
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
new file mode 100644
index 000000000000..20cf31ec6827
--- /dev/null
+++ b/phpstan-baseline.neon
@@ -0,0 +1,18 @@
+parameters:
+    ignoreErrors:
+        -
+            message: "#^Unsafe usage of new static\\(\\)\\.$#"
+            count: 1
+            path: src/Models/MailatorSchedule.php
+        -
+            message: "#^Unsafe usage of new static\\(\\)\\.$#"
+            count: 1
+            path: src/Replacers/ModelAttributesReplacer.php
+        -
+            message: "#^Unsafe usage of new static\\(\\)\\.$#"
+            count: 1
+            path: src/Exceptions/InvalidTemplateException.php
+        -
+            message: "#^Unsafe usage of new static\\(\\)\\.$#"
+            count: 1
+            path: src/Exceptions/InstanceException.php
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
new file mode 100644
index 000000000000..7feb852d2268
--- /dev/null
+++ b/phpstan.neon.dist
@@ -0,0 +1,12 @@
+includes:
+    - phpstan-baseline.neon
+
+parameters:
+    level: 2
+    paths:
+        - src
+        - config
+        - database
+    tmpDir: build/phpstan
+    checkModelProperties: true
+    checkMissingIterableValueType: false
diff --git a/psalm.xml.dist b/psalm.xml.dist
deleted file mode 100644
index a10c333533e1..000000000000
--- a/psalm.xml.dist
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0"?>
-<psalm
-    errorLevel="6"
-    findUnusedVariablesAndParams="true"
-    resolveFromConfigFile="true"
-    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xmlns="https://getpsalm.org/schema/config"
-    xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
->
-    <projectFiles>
-        <directory name="src"/>
-        <ignoreFiles>
-            <directory name="vendor"/>
-        </ignoreFiles>
-    </projectFiles>
-</psalm>
diff --git a/src/Constraints/AfterConstraint.php b/src/Constraints/AfterConstraint.php
index 754900a8b317..39c6ecea6cf1 100644
--- a/src/Constraints/AfterConstraint.php
+++ b/src/Constraints/AfterConstraint.php
@@ -18,33 +18,33 @@ public function canSend(MailatorSchedule $schedule, Collection $logs): bool
         }
 
         if ($schedule->toDays() > 0) {
-            if (now()->lt($schedule->timestampTarget()->addDays($schedule->toDays()))) {
+            if (now()->floorSeconds()->lt($schedule->timestampTarget()->addDays($schedule->toDays()))) {
                 return false;
             }
 
             return $schedule->isOnce()
-                ? $schedule->timestamp_target->diffInDays(now()) === $schedule->toDays()
-                : $schedule->timestamp_target->diffInDays(now()) > $schedule->toDays();
+                ? $schedule->timestamp_target->diffInDays(now()->floorSeconds()) === $schedule->toDays()
+                : $schedule->timestamp_target->diffInDays(now()->floorSeconds()) > $schedule->toDays();
         }
 
         if ($schedule->toHours() > 0) {
-            if (now()->lt($schedule->timestampTarget()->addHours($schedule->toHours()))) {
+            if (now()->floorSeconds()->lt($schedule->timestampTarget()->addHours($schedule->toHours()))) {
                 return false;
             }
 
             //till ends we should have at least toDays days
             return $schedule->isOnce()
-                ? $schedule->timestamp_target->diffInHours(now()) === $schedule->toHours()
-                : $schedule->timestamp_target->diffInHours(now()) > $schedule->toHours();
+                ? $schedule->timestamp_target->diffInHours(now()->floorSeconds()) === $schedule->toHours()
+                : $schedule->timestamp_target->diffInHours(now()->floorSeconds()) > $schedule->toHours();
         }
 
-        if (now()->lt($schedule->timestampTarget()->addMinutes($schedule->delay_minutes))) {
+        if (now()->floorSeconds()->lt($schedule->timestampTarget()->addMinutes($schedule->delay_minutes))) {
             return false;
         }
 
         //till ends we should have at least toDays days
         return $schedule->isOnce()
-            ? $schedule->timestamp_target->diffInHours(now()) === $schedule->delay_minutes
-            : $schedule->timestamp_target->diffInHours(now()) > $schedule->delay_minutes;
+            ? $schedule->timestamp_target->diffInHours(now()->floorSeconds()) === $schedule->delay_minutes
+            : $schedule->timestamp_target->diffInHours(now()->floorSeconds()) > $schedule->delay_minutes;
     }
 }
diff --git a/src/Constraints/BeforeConstraint.php b/src/Constraints/BeforeConstraint.php
index c95dd1fec94b..7ebe909207d5 100644
--- a/src/Constraints/BeforeConstraint.php
+++ b/src/Constraints/BeforeConstraint.php
@@ -18,13 +18,13 @@ public function canSend(MailatorSchedule $schedule, Collection $logs): bool
         }
 
         // if already expired
-        if ($schedule->timestamp_target->lte(now())) {
+        if ($schedule->timestamp_target->lte(now()->floorSeconds())) {
             return false;
         }
 
         //till ends we should have at least toDays days
         return $schedule->isOnce()
-            ? $schedule->timestamp_target->diffInDays(now()) === $schedule->toDays()
-            : $schedule->timestamp_target->diffInDays(now()) < $schedule->toDays();
+            ? $schedule->timestamp_target->diffInDays(now()->floorSeconds()) === $schedule->toDays()
+            : $schedule->timestamp_target->diffInDays(now()->floorSeconds()) < $schedule->toDays();
     }
 }
diff --git a/src/Models/Builders/MailatorSchedulerBuilder.php b/src/Models/Builders/MailatorSchedulerBuilder.php
index 2961058b7af5..91f801e7392b 100644
--- a/src/Models/Builders/MailatorSchedulerBuilder.php
+++ b/src/Models/Builders/MailatorSchedulerBuilder.php
@@ -4,6 +4,9 @@
 
 use Illuminate\Database\Eloquent\Builder;
 
+/**
+ * @mixin \Illuminate\Database\Query\Builder
+ */
 class MailatorSchedulerBuilder extends Builder
 {
     public function ready(): self
diff --git a/src/Models/Concerns/ConstraintsResolver.php b/src/Models/Concerns/ConstraintsResolver.php
index cbba71583679..afb9f35ff7bf 100644
--- a/src/Models/Concerns/ConstraintsResolver.php
+++ b/src/Models/Concerns/ConstraintsResolver.php
@@ -74,6 +74,7 @@ public function constraintsNotSatisfiedDescriptions(): array
             return collect($this->constraints)
                 ->map(fn (string $event) => unserialize($event))
                 ->filter(fn ($event) => is_subclass_of($event, Descriptionable::class))
+                ->filter(fn ($event) => is_subclass_of($event, SendScheduleConstraint::class))
                 ->filter(fn ($event) => ! $event->canSend($this, $this->logs))
                 ->reduce(function ($base, Descriptionable $descriable) {
                     return array_merge($base, $descriable::conditions());
diff --git a/src/Models/Concerns/WithUuid.php b/src/Models/Concerns/WithUuid.php
index d7cbb8884c2d..bb07b82ac889 100644
--- a/src/Models/Concerns/WithUuid.php
+++ b/src/Models/Concerns/WithUuid.php
@@ -6,11 +6,14 @@
 use Illuminate\Database\Eloquent\Model;
 use Illuminate\Support\Str;
 
+/**
+ * @property string $uuid
+ */
 trait WithUuid
 {
     public static function bootWithUuid()
     {
-        static::creating(function (Model $model) {
+        static::creating(function ($model) {
             if (! $model->uuid) {
                 $model->setAttribute('uuid', Str::uuid());
             }
diff --git a/src/Models/MailTemplate.php b/src/Models/MailTemplate.php
index 1b2fc164b864..1b852a1eb336 100644
--- a/src/Models/MailTemplate.php
+++ b/src/Models/MailTemplate.php
@@ -4,21 +4,24 @@
 
 use Binarcode\LaravelMailator\Models\Concerns\WithUuid;
 use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\HasMany;
 use Illuminate\Mail\Mailable;
 use Illuminate\Support\Collection;
 use TijsVerkoyen\CssToInlineStyles\CssToInlineStyles;
 
 /**
  * Class MailTemplate
- * @property string name
- * @property string subject
- * @property string from_email
- * @property string from_name
- * @property string html
- * @property string email_html
- * @property string webview_html
- * @property string mailable_class
- * @property string webvimailable_classew_html
+ * @property string $uuid
+ * @property string $name
+ * @property string $subject
+ * @property string $from_email
+ * @property string $from_name
+ * @property string $html
+ * @property string $email_html
+ * @property string $webview_html
+ * @property string $mailable_class
+ * @property string $webvimailable_classew_html
+ * @property-read Collection $placeholders
  * @package App\Models
  */
 class MailTemplate extends Model implements MailTemplateable
@@ -26,6 +29,7 @@ class MailTemplate extends Model implements MailTemplateable
     use WithUuid;
 
     protected $fillable = [
+        'uuid',
         'name',
         'from_email',
         'from_name',
@@ -36,7 +40,7 @@ class MailTemplate extends Model implements MailTemplateable
         'mailable_class',
     ];
 
-    public function placeholders()
+    public function placeholders(): HasMany
     {
         return $this->hasMany(
             config('mailator.templates.placeholder_model') ?? MailTemplatePlaceholder::class,
diff --git a/src/Models/MailTemplatePlaceholder.php b/src/Models/MailTemplatePlaceholder.php
index 2a49159f9bfa..89f17c3f330a 100644
--- a/src/Models/MailTemplatePlaceholder.php
+++ b/src/Models/MailTemplatePlaceholder.php
@@ -6,10 +6,12 @@
 
 /**
  * Class MailTemplate
- * @property string name
- * @property string description
- * @property int mail_template_id
- * @property-read MailTemplate mailTemplate
+ *
+ * @property string $name
+ * @property string $description
+ * @property int $mail_template_id
+ * @property-read MailTemplate $mailTemplate
+ *
  * @package App\Models
  */
 class MailTemplatePlaceholder extends Model
diff --git a/src/Models/MailatorLog.php b/src/Models/MailatorLog.php
index 298e7097d437..aa5c62d506a5 100644
--- a/src/Models/MailatorLog.php
+++ b/src/Models/MailatorLog.php
@@ -3,13 +3,18 @@
 namespace Binarcode\LaravelMailator\Models;
 
 use Carbon\Carbon;
+use Illuminate\Contracts\Support\Arrayable;
 use Illuminate\Database\Eloquent\Model;
 
 /**
  * Class MailatorLog
+ * @property string $name
  * @property string $status
  * @property Carbon $created_at
- * @property array recipients
+ * @property Arrayable|array $recipients
+ * @property Carbon $action_at
+ * @property Carbon $updated_at
+ * @property Carbon $exception
  * @package Binarcode\LaravelMailator\Models
  */
 class MailatorLog extends Model
diff --git a/src/Models/MailatorSchedule.php b/src/Models/MailatorSchedule.php
index 176132b5fbb9..f1423042e201 100644
--- a/src/Models/MailatorSchedule.php
+++ b/src/Models/MailatorSchedule.php
@@ -20,6 +20,7 @@
 use Closure;
 use Exception;
 use Illuminate\Contracts\Mail\Mailable;
+use Illuminate\Contracts\Support\Arrayable;
 use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Database\Eloquent\Model;
 use Illuminate\Database\Eloquent\Relations\HasMany;
@@ -43,12 +44,13 @@
  * @property string $mailable_class
  * @property numeric $delay_minutes
  * @property string $time_frame_origin
- * @property array $constraints
+ * @property Arrayable<SendScheduleConstraint> $constraints
  * @property Carbon $timestamp_target
  * @property array $recipients
  * @property string $action
  * @property Closure|string $when
  * @property Carbon $last_failed_at
+ * @property string $failure_reason
  * @property Carbon $last_sent_at
  * @property Carbon $completed_at
  * @property string $frequency_option
diff --git a/src/Replacers/Concerns/ReplaceModelAttributes.php b/src/Replacers/Concerns/ReplaceModelAttributes.php
index cc86b204259d..841030a57602 100644
--- a/src/Replacers/Concerns/ReplaceModelAttributes.php
+++ b/src/Replacers/Concerns/ReplaceModelAttributes.php
@@ -13,7 +13,7 @@
  * $replaceText => "first_name"
  * $model => {first_name: 'Eduard', ...}
  *
- * @return => "Welcome Eduard"
+ * return => "Welcome Eduard"
  */
 trait ReplaceModelAttributes
 {
diff --git a/tests/Feature/Models/MailatorScheduleTest.php b/tests/Feature/Models/MailatorScheduleTest.php
index c8962aa0444b..33d30d38d533 100644
--- a/tests/Feature/Models/MailatorScheduleTest.php
+++ b/tests/Feature/Models/MailatorScheduleTest.php
@@ -60,10 +60,10 @@ public function test_can_use_carbon_target_date_before(): void
 
         $scheduler->save();
 
-        MailatorSchedule::run();
-        Mail::assertNothingSent();
+//        MailatorSchedule::run();
+//        Mail::assertNothingSent();
 
-        TestTime::addDays(6);
+        $this->travelTo(now()->addDays(6));
         MailatorSchedule::run();
 
         Mail::assertSent(InvoiceReminderMailable::class, 1);

From e165af99805403223f1df6cb8a54500c1c9b72c8 Mon Sep 17 00:00:00 2001
From: Lupacescu Eduard <eduard.lupacescu@binarcode.com>
Date: Mon, 27 Jun 2022 22:05:01 +0300
Subject: [PATCH 13/34] before in hours (#2562)

* fix: restrict to laravel 9

* fix: upgrade

* fix: wip

* fix: wip

* fix: wip

* fix: wip

* fix: phpstan

* fix: wip

* fix: wip

* fix: wip

* fix: wip

* fix: allow hours before event to schedule mailator

* Fix styling

* fix: fixing after or equal

Co-authored-by: binaryk <binaryk@users.noreply.github.com>
---
 src/Constraints/AfterConstraint.php    |  2 +-
 src/Constraints/BeforeConstraint.php   | 32 +++++++--
 tests/Feature/BeforeConstraintTest.php | 91 ++++++++++++++++++++++++++
 3 files changed, 120 insertions(+), 5 deletions(-)
 create mode 100644 tests/Feature/BeforeConstraintTest.php

diff --git a/src/Constraints/AfterConstraint.php b/src/Constraints/AfterConstraint.php
index 39c6ecea6cf1..cafcc999fd95 100644
--- a/src/Constraints/AfterConstraint.php
+++ b/src/Constraints/AfterConstraint.php
@@ -38,7 +38,7 @@ public function canSend(MailatorSchedule $schedule, Collection $logs): bool
                 : $schedule->timestamp_target->diffInHours(now()->floorSeconds()) > $schedule->toHours();
         }
 
-        if (now()->floorSeconds()->lt($schedule->timestampTarget()->addMinutes($schedule->delay_minutes))) {
+        if (now()->floorSeconds()->lte($schedule->timestampTarget()->addMinutes($schedule->delay_minutes))) {
             return false;
         }
 
diff --git a/src/Constraints/BeforeConstraint.php b/src/Constraints/BeforeConstraint.php
index 7ebe909207d5..ae10d7f5eded 100644
--- a/src/Constraints/BeforeConstraint.php
+++ b/src/Constraints/BeforeConstraint.php
@@ -13,18 +13,42 @@ public function canSend(MailatorSchedule $schedule, Collection $logs): bool
             return true;
         }
 
-        if (is_null($schedule->timestamp_target)) {
+        if (is_null($schedule->timestampTarget())) {
             return true;
         }
 
         // if already expired
-        if ($schedule->timestamp_target->lte(now()->floorSeconds())) {
+        if ($schedule->timestampTarget()->lte(now()->floorSeconds())) {
             return false;
         }
 
+        if ($schedule->toDays() > 0) {
+            if (now()->floorSeconds()->gt($schedule->timestampTarget()->addDays($schedule->toDays()))) {
+                return false;
+            }
+
+            //till ends we should have at least toDays days
+            return $schedule->isOnce()
+                ? $schedule->timestampTarget()->diffInDays(now()->floorSeconds()) === $schedule->toDays()
+                : $schedule->timestampTarget()->diffInDays(now()->floorSeconds()) < $schedule->toDays();
+        }
+
+        if ($schedule->toHours() > 0) {
+            if (now()->floorSeconds()->gt($schedule->timestampTarget()->addHours($schedule->toHours()))) {
+                return false;
+            }
+
+            //till ends we should have at least toHours days
+            return $schedule->isOnce()
+                ? $schedule->timestamp_target->diffInHours(now()->floorSeconds()) === $schedule->toHours()
+                : $schedule->timestamp_target->diffInHours(now()->floorSeconds()) < $schedule->toHours();
+        }
+
+
+
         //till ends we should have at least toDays days
         return $schedule->isOnce()
-            ? $schedule->timestamp_target->diffInDays(now()->floorSeconds()) === $schedule->toDays()
-            : $schedule->timestamp_target->diffInDays(now()->floorSeconds()) < $schedule->toDays();
+            ? $schedule->timestampTarget()->diffInDays(now()->floorSeconds()) === $schedule->toDays()
+            : $schedule->timestampTarget()->diffInDays(now()->floorSeconds()) < $schedule->toDays();
     }
 }
diff --git a/tests/Feature/BeforeConstraintTest.php b/tests/Feature/BeforeConstraintTest.php
new file mode 100644
index 000000000000..210776c4d63f
--- /dev/null
+++ b/tests/Feature/BeforeConstraintTest.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Binarcode\LaravelMailator\Tests\Feature;
+
+use Binarcode\LaravelMailator\Constraints\BeforeConstraint;
+use Binarcode\LaravelMailator\Models\MailatorSchedule;
+use Binarcode\LaravelMailator\Tests\Fixtures\InvoiceReminderMailable;
+use Binarcode\LaravelMailator\Tests\TestCase;
+use Illuminate\Support\Facades\Mail;
+
+class BeforeConstraintTest extends TestCase
+{
+    public function test_past_target_with_before_now_passed_after_constraint_day_bases(): void
+    {
+        Mail::fake();
+        Mail::assertNothingSent();
+
+        $scheduler = MailatorSchedule::init('Invoice reminder.')
+            ->recipients($mail = 'zoo@bar.com')
+            ->mailable(
+                (new InvoiceReminderMailable())->to('foo@bar.com')
+            )
+            ->days(1)
+            ->before(now()->addDays(2));
+
+        $scheduler->save();
+
+        $this->travel(1)->days();
+
+        self::assertFalse(
+            $scheduler->fresh()->isFutureAction()
+        );
+
+        $can = app(BeforeConstraint::class)->canSend(
+            $scheduler,
+            $scheduler->logs
+        );
+
+        self::assertTrue(
+            $can
+        );
+    }
+
+    public function test_past_target_with_before_now_passed_after_constraint_hourly_bases(): void
+    {
+        Mail::fake();
+        Mail::assertNothingSent();
+
+        $scheduler = MailatorSchedule::init('Invoice reminder.')
+            ->recipients($mail = 'zoo@bar.com')
+            ->mailable(
+                (new InvoiceReminderMailable())->to('foo@bar.com')
+            )
+            ->hours(1)
+            ->before(now()->addHours(3));
+
+        $scheduler->save();
+
+        $this->travel(1)->hours();
+
+        $this->travel(1)->hours();
+
+        $can = app(
+            BeforeConstraint::class
+        )->canSend(
+            $scheduler,
+            $scheduler->logs
+        );
+
+        self::assertTrue(
+            $can
+        );
+
+        $this->travel(1)->hours();
+
+        $can = app(
+            BeforeConstraint::class
+        )->canSend(
+            $scheduler,
+            $scheduler->logs
+        );
+
+        self::assertFalse(
+            $scheduler->fresh()->isFutureAction()
+        );
+
+        self::assertFalse(
+            $can
+        );
+    }
+}

From 3fd3121f7ed216da3d19452fb4f3506749fae268 Mon Sep 17 00:00:00 2001
From: Eduard Lupacescu <eduard.lupacescu@binarcode.com>
Date: Mon, 8 Aug 2022 13:58:18 +0300
Subject: [PATCH 14/34] fix: docs

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 8d32d091ae93..fb2a0f1fd3e2 100644
--- a/README.md
+++ b/README.md
@@ -249,7 +249,7 @@ Mailator provides the `Binarcode\LaravelMailator\Models\Concerns\HasMailatorSche
 By default, scheduler run the action, or send the email only once. You can change that, and use a daily reminder till the constraint returns a truth condition:
 
 ```php
-use Binarcode\LaravelMailator\Scheduler;use Binarcode\LaravelMailator\Tests\Fixtures\InvoiceReminderMailable;
+use Binarcode\LaravelMailator\Scheduler;
 
 // 2021-20-06 - 20 June 2021
 $expirationDate = $invoice->expire_at;

From b674c739113cf808daf35bccbcd875d4c5c603a7 Mon Sep 17 00:00:00 2001
From: Lupacescu Eduard <eduard.lupacescu@binarcode.com>
Date: Tue, 9 Aug 2022 22:43:51 +0300
Subject: [PATCH 15/34] Update README.md

---
 README.md | 140 +++++++++++++++++++++++++++---------------------------
 1 file changed, 70 insertions(+), 70 deletions(-)

diff --git a/README.md b/README.md
index fb2a0f1fd3e2..cbdc1d369e38 100644
--- a/README.md
+++ b/README.md
@@ -28,79 +28,13 @@ Publish config: `a vendor:publish --tag=mailator-config`
 
 It has mainly 2 directions of usage:
 
-1. Email Templates & Placeholders
+1. Schedule emails sending (or actions triggering)
 
-2. Email Scheduler
-
-## Templating
-
-To create an email template:
-
-``` php
-$template = Binarcode\LaravelMailator\Models\MailTemplate::create([
-    'name' => 'Welcome Email.',
-    'from_email' => 'from@bar.com',
-    'from_name' => 'From Bar',
-    'subject' => 'Welcome to Mailator.',
-    'html' => '<h1>Welcome to the party!</h1>',
-]);
-```
-
-Adding some placeholders with description to this template:
-
-```php
-$template->placeholders()->create(
-    [
-        'name' => '::name::',
-        'description' => 'Name',
-    ],
-);
-```
-
-To use the template, you simply have to add the `WithMailTemplate` trait to your mailable.
-
-This will enforce you to implement the `getReplacers` method, this should return an array of replacers to your template.
-The array may contain instances of `Binarcode\LaravelMailator\Replacers\Replacer` or even `Closure` instances.
-
-Mailator shipes with a builtin replacer `ModelAttributesReplacer`, it will automaticaly replace attributes from the
-model you provide to placeholders.
-
-The last step is how to say to your mailable what template to use. This could be done into the build method as shown
-bellow:
-
-```php
-class WelcomeMailatorMailable extends Mailable
-{
-    use Binarcode\LaravelMailator\Support\WithMailTemplate;
-    
-    private Model $user;
-    
-    public function __construct(Model $user)
-    {
-        $this->user = $user;
-    }
-    
-    public function build()
-    {
-        return $this->template(MailTemplate::firstWhere('name', 'Welcome Email.'));
-    }
-
-    public function getReplacers(): array
-    {
-        return [
-            Binarcode\LaravelMailator\Replacers\ModelAttributesReplacer::makeWithModel($this->user),
-
-            function($html) {
-                //
-            }       
-        ];
-    }
-}
-```
+2. Email Templates & Placeholders
 
 ## Scheduler
 
-To set up a mail to be sent after or before an event, you can do this by using `Scheduler` facade.
+To set up a mail to be sent after or before an event, you can do this by using the `Scheduler` facade.
 
 Firstly lets set up a mail scheduler:
 
@@ -354,6 +288,73 @@ Package provides the `Binarcode\LaravelMailator\Console\MailatorSchedulerCommand
 $schedule->command(MailatorSchedulerCommand::class)->everyThirtyMinutes();
 ```
 
+
+## Templating
+
+To create an email template:
+
+``` php
+$template = Binarcode\LaravelMailator\Models\MailTemplate::create([
+    'name' => 'Welcome Email.',
+    'from_email' => 'from@bar.com',
+    'from_name' => 'From Bar',
+    'subject' => 'Welcome to Mailator.',
+    'html' => '<h1>Welcome to the party!</h1>',
+]);
+```
+
+Adding some placeholders with description to this template:
+
+```php
+$template->placeholders()->create(
+    [
+        'name' => '::name::',
+        'description' => 'Name',
+    ],
+);
+```
+
+To use the template, you simply have to add the `WithMailTemplate` trait to your mailable.
+
+This will enforce you to implement the `getReplacers` method, this should return an array of replacers to your template.
+The array may contain instances of `Binarcode\LaravelMailator\Replacers\Replacer` or even `Closure` instances.
+
+Mailator shipes with a builtin replacer `ModelAttributesReplacer`, it will automaticaly replace attributes from the
+model you provide to placeholders.
+
+The last step is how to say to your mailable what template to use. This could be done into the build method as shown
+bellow:
+
+```php
+class WelcomeMailatorMailable extends Mailable
+{
+    use Binarcode\LaravelMailator\Support\WithMailTemplate;
+    
+    private Model $user;
+    
+    public function __construct(Model $user)
+    {
+        $this->user = $user;
+    }
+    
+    public function build()
+    {
+        return $this->template(MailTemplate::firstWhere('name', 'Welcome Email.'));
+    }
+
+    public function getReplacers(): array
+    {
+        return [
+            Binarcode\LaravelMailator\Replacers\ModelAttributesReplacer::makeWithModel($this->user),
+
+            function($html) {
+                //
+            }       
+        ];
+    }
+}
+```
+
 ### Testing
 
 ``` bash
@@ -381,4 +382,3 @@ tracker.
 ## License
 
 The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
-

From 9c8de7f00d366c479ed245d8c3a869b54ec1cdc6 Mon Sep 17 00:00:00 2001
From: Lupacescu Eduard <eduard.lupacescu@binarcode.com>
Date: Tue, 9 Aug 2022 22:46:58 +0300
Subject: [PATCH 16/34] Update README.md

---
 README.md | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md
index cbdc1d369e38..0d8dc9005ceb 100644
--- a/README.md
+++ b/README.md
@@ -36,18 +36,18 @@ It has mainly 2 directions of usage:
 
 To set up a mail to be sent after or before an event, you can do this by using the `Scheduler` facade.
 
-Firstly lets set up a mail scheduler:
+Here is an example of how to send the `invoice reminder email` `3 days` before the `$invoice->due_date`:
 
 ```php
 use Binarcode\LaravelMailator\Tests\Fixtures\InvoiceReminderMailable;
 use Binarcode\LaravelMailator\Tests\Fixtures\SerializedConditionCondition;
 
 Binarcode\LaravelMailator\Scheduler::init('Invoice reminder.')
-    ->mailable(new InvoiceReminderMailable())
+    ->mailable(new InvoiceReminderMailable($invoice))
     ->recipients('foo@binarcode.com', 'baz@binarcode.com')
-    ->constraint(new SerializedConditionCondition(User::first()))
-    ->days(1)
-    ->before(now()->addYear())
+    ->constraint(new SerializedConditionCondition($invoice))
+    ->days(3)
+    ->before($invoice->due_date)
     ->save();
 ```
 

From 2e89c76e6f9d5f377a3297a47b022a663adb76f9 Mon Sep 17 00:00:00 2001
From: Lupacescu Eduard <eduard.lupacescu@binarcode.com>
Date: Tue, 9 Aug 2022 22:51:30 +0300
Subject: [PATCH 17/34] Update README.md

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 0d8dc9005ceb..c93447cf2220 100644
--- a/README.md
+++ b/README.md
@@ -171,7 +171,7 @@ and then in the `Invoice` model you can get all emails related to it:
 // app/Models/Invoice.php
 public function schedulers() 
 {
-        return $this->morphMany(Binarcode\LaravelMailator\Models\MailatorSchedule::class, 'targetable');
+    return $this->morphMany(Binarcode\LaravelMailator\Models\MailatorSchedule::class, 'targetable');
 }
 ...
 ```

From 7c3fa06e07bdb960b1d8e78f84319e422d6ce2b9 Mon Sep 17 00:00:00 2001
From: Lupacescu Eduard <eduard.lupacescu@binarcode.com>
Date: Mon, 22 Aug 2022 18:22:15 +0300
Subject: [PATCH 18/34] Update README.md

---
 README.md | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/README.md b/README.md
index c93447cf2220..cc834d55f1de 100644
--- a/README.md
+++ b/README.md
@@ -371,8 +371,7 @@ Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
 
 ### Security
 
-If you discover any security related issues, please email eduard.lupacescu@binarcode.com instead of using the issue
-tracker.
+If you discover any security related issues, please email eduard.lupacescu@binarcode.com or [message me on twitter](https://twitter.com/LupacescuEuard) instead of using the issue tracker.
 
 ## Credits
 

From 1a6a63c757985bf4289b4c168d2442a752fb0edb Mon Sep 17 00:00:00 2001
From: Daniel Alm <Daniel.Alm@ForumD.net>
Date: Tue, 13 Sep 2022 13:43:31 +0200
Subject: [PATCH 19/34] Fix a small typo in the README (#2563)

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index cc834d55f1de..15de256b6d3b 100644
--- a/README.md
+++ b/README.md
@@ -101,7 +101,7 @@ The `after` constraint accept a `CarbonInterface` as well. The difference, is th
 
 ### Constraint
 
-The `contraint()` method accept an instance of `Binarcode\LaravelMailator\Constraints\SendScheduleConstraint`. Each constraint will be called when the scheduler will try to send the email. If all constraints return true, the email will be sent.
+The `constraint()` method accept an instance of `Binarcode\LaravelMailator\Constraints\SendScheduleConstraint`. Each constraint will be called when the scheduler will try to send the email. If all constraints return true, the email will be sent.
 
 The `constraint()` method could be called many times, and each constraint will be stored. 
 

From 75f4efd99e30500027124526852aff965a9005dc Mon Sep 17 00:00:00 2001
From: Lupacescu Eduard <eduard.lupacescu@binarcode.com>
Date: Fri, 17 Feb 2023 13:33:24 +0200
Subject: [PATCH 20/34] 4.1.1 (#2568)

* fix: eager loading logs when running the schedule:run command

* fix: wip
---
 .github/workflows/release.yml       | 22 ++++++++++++++++++++++
 src/Actions/RunSchedulersAction.php |  1 +
 2 files changed, 23 insertions(+)
 create mode 100644 .github/workflows/release.yml

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 000000000000..99068bec3900
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,22 @@
+name: Release
+
+on:
+    pull_request:
+        types:
+            - closed
+        branches:
+            - "main"
+
+jobs:
+    release:
+        name: Create release
+        if: github.event.pull_request.merged == true
+        runs-on: "ubuntu-latest"
+        steps:
+            - uses: actions/checkout@v2
+            - uses: ncipollo/release-action@v1
+              with:
+                  name: ${{ github.event.pull_request.title }}
+                  tag: ${{ github.event.pull_request.title }}
+                  body: ${{ github.event.pull_request.body }}
+                  prerelease: false
diff --git a/src/Actions/RunSchedulersAction.php b/src/Actions/RunSchedulersAction.php
index 120ea4354e3b..94e6dd1d3bf0 100644
--- a/src/Actions/RunSchedulersAction.php
+++ b/src/Actions/RunSchedulersAction.php
@@ -13,6 +13,7 @@ public function __invoke()
     {
         static::scheduler()::query()
             ->ready()
+            ->with('logs')
             ->cursor()
             ->filter(fn (MailatorSchedule $schedule) => $schedule->shouldSend())
             ->each(fn (MailatorSchedule $schedule) => $schedule->execute());

From f067969184a03814d32c7765cafe714290cf8975 Mon Sep 17 00:00:00 2001
From: Eduard Lupacescu <eduard.lupacescu@binarcode.com>
Date: Fri, 17 Feb 2023 13:34:07 +0200
Subject: [PATCH 21/34] fix: wip

---
 .github/workflows/release.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 99068bec3900..e1d83a2067a5 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -5,7 +5,7 @@ on:
         types:
             - closed
         branches:
-            - "main"
+            - "master"
 
 jobs:
     release:

From a8861b20daf2d337175a6c5a721b941023dcbcee Mon Sep 17 00:00:00 2001
From: Lupacescu Eduard <eduard.lupacescu@binarcode.com>
Date: Fri, 17 Feb 2023 14:50:09 +0200
Subject: [PATCH 22/34] fix: load only missing logs (#2570)

---
 src/Models/MailatorSchedule.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Models/MailatorSchedule.php b/src/Models/MailatorSchedule.php
index f1423042e201..d6b30afa6c3b 100644
--- a/src/Models/MailatorSchedule.php
+++ b/src/Models/MailatorSchedule.php
@@ -347,7 +347,7 @@ public function logs(): HasMany
     public function shouldSend(): bool
     {
         try {
-            $this->load('logs');
+            $this->loadMissing('logs');
 
             if (! $this->configurationsPasses()) {
                 return false;

From c2c6b1d839f4e7dae77e1e4d6decc39727eb62c4 Mon Sep 17 00:00:00 2001
From: Victor Malai <malai9696@gmail.com>
Date: Fri, 17 Feb 2023 15:14:05 +0200
Subject: [PATCH 23/34] feat: add laravel 10 support (#2569)

---
 .github/workflows/phpstan.yml   |  2 +-
 .github/workflows/run-tests.yml |  8 ++++----
 composer.json                   | 12 ++++++------
 phpunit.xml.dist                | 11 -----------
 4 files changed, 11 insertions(+), 22 deletions(-)

diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml
index 5559cee274a4..0323ffc4fb6e 100644
--- a/.github/workflows/phpstan.yml
+++ b/.github/workflows/phpstan.yml
@@ -16,7 +16,7 @@ jobs:
             - name: Setup PHP
               uses: shivammathur/setup-php@v2
               with:
-                  php-version: '8.0'
+                  php-version: '8.1'
                   coverage: none
 
             - name: Install composer dependencies
diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index bf7e0e2523c6..b9550bbbc61d 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -9,12 +9,12 @@ jobs:
             fail-fast: true
             matrix:
                 os: [ubuntu-latest, windows-latest]
-                php: [8.0, 8.1]
-                laravel: [^9.0]
+                php: [8.1]
+                laravel: [^10.0]
                 stability: [prefer-stable]
                 include:
-                    - laravel: ^9.0
-                      testbench: ^7.0
+                    - laravel: ^10.0
+                      testbench: ^8.0
 
         name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
 
diff --git a/composer.json b/composer.json
index 5e8eabb94bf0..b961a953899b 100644
--- a/composer.json
+++ b/composer.json
@@ -17,17 +17,17 @@
         }
     ],
     "require": {
-        "php": "^8.0",
-        "illuminate/support": "^9.0",
+        "php": "^8.0|^8.1",
+        "illuminate/support": "^9.0|^10.0",
         "opis/closure": "^3.6"
     },
     "require-dev": {
-        "brianium/paratest": "^6.2",
-        "nunomaduro/collision": "^6.0",
+        "brianium/paratest": "^6.2|^7.0.6",
+        "nunomaduro/collision": "^5.3|^6.1|^7.0",
         "nunomaduro/larastan": "^2.0",
-        "orchestra/testbench": "^7.0",
+        "orchestra/testbench": "^8.0",
         "phpstan/extension-installer": "^1.1",
-        "phpunit/phpunit": "^9.5",
+        "phpunit/phpunit": "^10.0",
         "spatie/laravel-ray": "^1.9",
         "spatie/test-time": "^1.2"
     },
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index d46bc648172c..46a72651f7ff 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,22 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <phpunit bootstrap="vendor/autoload.php"
          backupGlobals="false"
-         backupStaticAttributes="false"
          colors="true"
-         verbose="true"
-         convertErrorsToExceptions="true"
-         convertNoticesToExceptions="true"
-         convertWarningsToExceptions="true"
-         processIsolation="false"
          stopOnFailure="false">
     <testsuites>
         <testsuite name="BinarCode Test Suite">
             <directory>tests</directory>
         </testsuite>
     </testsuites>
-    <filter>
-        <whitelist>
-            <directory suffix=".php">src/</directory>
-        </whitelist>
-    </filter>
 </phpunit>

From 2fa7d2f5a1d5f808247234ea7cf1982a69971a3d Mon Sep 17 00:00:00 2001
From: Victor Malai <malai9696@gmail.com>
Date: Tue, 18 Apr 2023 18:51:00 +0300
Subject: [PATCH 24/34] feat: add prune command (#2571)

* feat: add prune command

* Fix styling

---------

Co-authored-by: maloun96 <maloun96@users.noreply.github.com>
---
 ...ail_logs_table_add_cascade_delete.php.stub | 22 ++++++++++++++
 .../Commands/PruneMailatorLogsCommand.php     | 18 ++++++++++++
 .../Commands/PruneMailatorScheduleCommand.php | 18 ++++++++++++
 src/LaravelMailatorServiceProvider.php        | 13 ++++++++-
 src/Models/Concerns/WithPrune.php             | 29 +++++++++++++++++++
 src/Models/MailatorLog.php                    |  3 ++
 src/Models/MailatorSchedule.php               |  2 ++
 7 files changed, 104 insertions(+), 1 deletion(-)
 create mode 100644 database/migrations/alter_mail_logs_table_add_cascade_delete.php.stub
 create mode 100644 src/Console/Commands/PruneMailatorLogsCommand.php
 create mode 100644 src/Console/Commands/PruneMailatorScheduleCommand.php
 create mode 100644 src/Models/Concerns/WithPrune.php

diff --git a/database/migrations/alter_mail_logs_table_add_cascade_delete.php.stub b/database/migrations/alter_mail_logs_table_add_cascade_delete.php.stub
new file mode 100644
index 000000000000..d129309b9c0a
--- /dev/null
+++ b/database/migrations/alter_mail_logs_table_add_cascade_delete.php.stub
@@ -0,0 +1,22 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+use Binarcode\LaravelMailator\Models\MailatorLog;
+use Binarcode\LaravelMailator\Models\MailatorSchedule;
+
+class AlterMailLogsTableAddCascadeDelete extends Migration
+{
+    public function up()
+    {
+        Schema::table(config('mailator.logs_table', 'mailator_logs'), function (Blueprint $table) {
+            $table->dropForeign(['mailator_schedule_id']);
+
+            $table->foreign('mailator_schedule_id')
+                ->references('id')
+                ->on(config('mailator.schedulers_table_name', 'mailator_schedulers'))
+                ->cascadeOnDelete();
+        });
+    }
+}
diff --git a/src/Console/Commands/PruneMailatorLogsCommand.php b/src/Console/Commands/PruneMailatorLogsCommand.php
new file mode 100644
index 000000000000..074ab39cb02c
--- /dev/null
+++ b/src/Console/Commands/PruneMailatorLogsCommand.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Binarcode\LaravelMailator\Console\Commands;
+
+use Binarcode\LaravelMailator\Models\MailatorLog;
+use Illuminate\Console\Command;
+
+class PruneMailatorLogsCommand extends Command
+{
+    protected $signature = 'mailator:logs:prune {--days=60 : The number of days to retain Mailator logs}';
+
+    protected $description = 'Prune stale entries from the Mailator logs.';
+
+    public function handle(): void
+    {
+        $this->info(MailatorLog::prune(now()->subDays((int)$this->option('days'))) . ' entries pruned.');
+    }
+}
diff --git a/src/Console/Commands/PruneMailatorScheduleCommand.php b/src/Console/Commands/PruneMailatorScheduleCommand.php
new file mode 100644
index 000000000000..c3ce2ad2815e
--- /dev/null
+++ b/src/Console/Commands/PruneMailatorScheduleCommand.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Binarcode\LaravelMailator\Console\Commands;
+
+use Binarcode\LaravelMailator\Models\MailatorSchedule;
+use Illuminate\Console\Command;
+
+class PruneMailatorScheduleCommand extends Command
+{
+    protected $signature = 'mailator:prune {--days=60 : The number of days to retain Mailator data}';
+
+    protected $description = 'Prune stale entries from the Mailator data.';
+
+    public function handle(): void
+    {
+        $this->info(MailatorSchedule::prune(now()->subDays((int)$this->option('days'))) . ' entries pruned.');
+    }
+}
diff --git a/src/LaravelMailatorServiceProvider.php b/src/LaravelMailatorServiceProvider.php
index 0e1b1f89119d..0df291d12bd6 100644
--- a/src/LaravelMailatorServiceProvider.php
+++ b/src/LaravelMailatorServiceProvider.php
@@ -4,6 +4,8 @@
 
 use Binarcode\LaravelMailator\Console\Commands\GarbageCollectorCommand;
 use Binarcode\LaravelMailator\Console\Commands\MailatorSchedulerCommand;
+use Binarcode\LaravelMailator\Console\Commands\PruneMailatorLogsCommand;
+use Binarcode\LaravelMailator\Console\Commands\PruneMailatorScheduleCommand;
 use Binarcode\LaravelMailator\Models\MailatorLog;
 use Binarcode\LaravelMailator\Models\MailatorSchedule;
 use Binarcode\LaravelMailator\Models\MailTemplate;
@@ -48,9 +50,16 @@ public function boot()
 
             if (! class_exists('CreateMailatorTables')) {
                 $this->publishes([
-                    __DIR__ . '/../database/migrations/create_mailator_tables.php.stub' => database_path('migrations/' . date('Y_m_d_His', time()) . '_create_mailator_tables.php'),
+                    __DIR__ . '/../database/migrations/create_mailator_tables.php.stub' => database_path('migrations/' . date('Y_m_d_His', now()->subMinute(1)->timestamp) . '_create_mailator_tables.php'),
                 ], 'mailator-migrations');
             }
+
+            if (! class_exists('AlterMailLogsTableAddCascadeDelete')) {
+                $this->publishes([
+                    __DIR__ . '/../database/migrations/alter_mail_logs_table_add_cascade_delete.php.stub' => database_path('migrations/' . date('Y_m_d_His', now()->timestamp) . '_alter_mail_logs_table_add_cascade_delete.php'),
+                ], 'mailator-migrations');
+            }
+
             // Publishing the views.
             $this->publishes([
                 __DIR__.'/../resources/views/publish' => resource_path('views/vendor/laravel-mailator'),
@@ -70,6 +79,8 @@ public function boot()
             $this->commands([
                  GarbageCollectorCommand::class,
                  MailatorSchedulerCommand::class,
+                 PruneMailatorScheduleCommand::class,
+                 PruneMailatorLogsCommand::class,
              ]);
         }
     }
diff --git a/src/Models/Concerns/WithPrune.php b/src/Models/Concerns/WithPrune.php
new file mode 100644
index 000000000000..a66a4581420e
--- /dev/null
+++ b/src/Models/Concerns/WithPrune.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Binarcode\LaravelMailator\Models\Concerns;
+
+use DateTimeInterface;
+use Illuminate\Database\Eloquent\Model;
+
+/**
+ * @mixin Model
+ */
+trait WithPrune
+{
+    public static function prune(DateTimeInterface $before)
+    {
+        $query = static::query()
+            ->with('logs')
+            ->where('created_at', '<', $before);
+
+        $totalDeleted = 0;
+
+        do {
+            $deleted = $query->take(1000)->delete();
+
+            $totalDeleted += $deleted;
+        } while ($deleted !== 0);
+
+        return $totalDeleted;
+    }
+}
diff --git a/src/Models/MailatorLog.php b/src/Models/MailatorLog.php
index aa5c62d506a5..36413806fa52 100644
--- a/src/Models/MailatorLog.php
+++ b/src/Models/MailatorLog.php
@@ -2,6 +2,7 @@
 
 namespace Binarcode\LaravelMailator\Models;
 
+use Binarcode\LaravelMailator\Models\Concerns\WithPrune;
 use Carbon\Carbon;
 use Illuminate\Contracts\Support\Arrayable;
 use Illuminate\Database\Eloquent\Model;
@@ -19,6 +20,8 @@
  */
 class MailatorLog extends Model
 {
+    use WithPrune;
+
     public function getTable()
     {
         return config('mailator.logs_table', 'mailator_logs');
diff --git a/src/Models/MailatorSchedule.php b/src/Models/MailatorSchedule.php
index d6b30afa6c3b..97fad351df8f 100644
--- a/src/Models/MailatorSchedule.php
+++ b/src/Models/MailatorSchedule.php
@@ -13,6 +13,7 @@
 use Binarcode\LaravelMailator\Models\Concerns\ConstraintsResolver;
 use Binarcode\LaravelMailator\Models\Concerns\HasFuture;
 use Binarcode\LaravelMailator\Models\Concerns\HasTarget;
+use Binarcode\LaravelMailator\Models\Concerns\WithPrune;
 use Binarcode\LaravelMailator\Support\ClassResolver;
 use Binarcode\LaravelMailator\Support\ConverterEnum;
 use Carbon\Carbon;
@@ -64,6 +65,7 @@ class MailatorSchedule extends Model
     use HasTarget;
     use HasFuture;
     use ClassResolver;
+    use WithPrune;
 
     public function getTable()
     {

From afa2b77f3b5d7726de61b8bcaf359c2412f22767 Mon Sep 17 00:00:00 2001
From: binaryk <binaryk@users.noreply.github.com>
Date: Tue, 18 Apr 2023 15:51:24 +0000
Subject: [PATCH 25/34] Fix styling

---
 tests/Feature/Models/MailatorScheduleTest.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/tests/Feature/Models/MailatorScheduleTest.php b/tests/Feature/Models/MailatorScheduleTest.php
index 33d30d38d533..6295b5738e10 100644
--- a/tests/Feature/Models/MailatorScheduleTest.php
+++ b/tests/Feature/Models/MailatorScheduleTest.php
@@ -60,8 +60,8 @@ public function test_can_use_carbon_target_date_before(): void
 
         $scheduler->save();
 
-//        MailatorSchedule::run();
-//        Mail::assertNothingSent();
+        //        MailatorSchedule::run();
+        //        Mail::assertNothingSent();
 
         $this->travelTo(now()->addDays(6));
         MailatorSchedule::run();

From 1e4909ca1b37c88c6419046965c43198a2437895 Mon Sep 17 00:00:00 2001
From: Papuc Vasile <32643188+CaReS0107@users.noreply.github.com>
Date: Fri, 30 Jun 2023 15:35:19 +0200
Subject: [PATCH 26/34] Add hours precision when email is dispatched (#2576)

* Add hour precision when mailator is dispached

* Added readme documentation & fix tests

* Fix styling

---------

Co-authored-by: Vasile Papuc <vasile.papuc@binarcode.com>
Co-authored-by: CaReS0107 <CaReS0107@users.noreply.github.com>
---
 .github/workflows/run-tests.yml               |  2 +-
 README.md                                     | 20 +++++
 .../create_mailator_tables.php.stub           |  2 +-
 src/Actions/RunSchedulersAction.php           |  2 +-
 .../HoursSchedulerCheckerConstraint.php       | 18 +++++
 src/Models/Concerns/ConstraintsResolver.php   |  7 +-
 src/Models/MailatorSchedule.php               | 22 +++++-
 tests/Feature/Models/MailatorScheduleTest.php |  2 +-
 tests/Feature/WithWeekendsConstraintTest.php  | 79 +++++++++++++++++++
 tests/TestCase.php                            |  1 -
 10 files changed, 145 insertions(+), 10 deletions(-)
 create mode 100644 src/Constraints/HoursSchedulerCheckerConstraint.php
 create mode 100644 tests/Feature/WithWeekendsConstraintTest.php

diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index b9550bbbc61d..dc0089624aa3 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -40,4 +40,4 @@ jobs:
                   composer update --${{ matrix.stability }} --prefer-dist --no-interaction
 
             - name: Execute tests
-              run: ./vendor/bin/testbench package:test --parallel --no-coverage
+              run: ./vendor/bin/testbench package:test
diff --git a/README.md b/README.md
index 15de256b6d3b..571b039ba668 100644
--- a/README.md
+++ b/README.md
@@ -99,6 +99,26 @@ The `after` constraint accept a `CarbonInterface` as well. The difference, is th
     ->after($order->created_at)
 ```
 
+### Precision
+Hour Precision
+
+The `precision` method provides fine-grained control over when emails are sent using MailatorSchedule. It allows you to specify specific hours or intervals within a 24-hour period. Here's an example of how to use the precision method:
+```php
+    ->many()
+    ->precision([3-4])
+```
+This will schedule the email dispatch between '03:00:00' AM and '04:59:59' AM.
+
+or
+```php
+    ->once()
+    ->precision([1])
+```
+This will schedule the email dispatch between '01:00:00' AM and '01:59:59'.
+
+You can continue this pattern to specify the desired hour(s) within the range of 1 to 24.
+
+**Important: When using the precision feature in the Mailator scheduler, it is recommended to set the scheduler to run at intervals that are less than an hour. You can choose intervals such as every 5 minutes, 10 minutes, 30 minutes, or any other desired duration.**
 ### Constraint
 
 The `constraint()` method accept an instance of `Binarcode\LaravelMailator\Constraints\SendScheduleConstraint`. Each constraint will be called when the scheduler will try to send the email. If all constraints return true, the email will be sent.
diff --git a/database/migrations/create_mailator_tables.php.stub b/database/migrations/create_mailator_tables.php.stub
index 60eda4f89af6..d7dc7ba38340 100644
--- a/database/migrations/create_mailator_tables.php.stub
+++ b/database/migrations/create_mailator_tables.php.stub
@@ -30,7 +30,7 @@ class CreateMailatorTables extends Migration
             $table->json('recipients')->nullable();
             $table->text('when')->nullable();
             $table->string('frequency_option')->default(MailatorSchedule::FREQUENCY_OPTIONS_ONCE)->comment('How often send email notification.');
-
+            $table->json('schedule_at_hours')->nullable();
             $table->timestamp('last_sent_at')->nullable();
             $table->timestamp('last_failed_at')->nullable();
             $table->timestamp('completed_at')->nullable();
diff --git a/src/Actions/RunSchedulersAction.php b/src/Actions/RunSchedulersAction.php
index 94e6dd1d3bf0..d6a7d63efbcd 100644
--- a/src/Actions/RunSchedulersAction.php
+++ b/src/Actions/RunSchedulersAction.php
@@ -9,7 +9,7 @@ class RunSchedulersAction
 {
     use ClassResolver;
 
-    public function __invoke()
+    public function __invoke(): void
     {
         static::scheduler()::query()
             ->ready()
diff --git a/src/Constraints/HoursSchedulerCheckerConstraint.php b/src/Constraints/HoursSchedulerCheckerConstraint.php
new file mode 100644
index 000000000000..2677f6a5e6de
--- /dev/null
+++ b/src/Constraints/HoursSchedulerCheckerConstraint.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Binarcode\LaravelMailator\Constraints;
+
+use Binarcode\LaravelMailator\Models\MailatorSchedule;
+use Illuminate\Support\Collection;
+
+class HoursSchedulerCheckerConstraint implements SendScheduleConstraint
+{
+    public function canSend(MailatorSchedule $schedule, Collection $logs): bool
+    {
+        if (! $schedule->hasPrecision()) {
+            return true;
+        }
+
+        return in_array(now()->hour, $schedule->schedule_at_hours);
+    }
+}
diff --git a/src/Models/Concerns/ConstraintsResolver.php b/src/Models/Concerns/ConstraintsResolver.php
index afb9f35ff7bf..72ed66968a30 100644
--- a/src/Models/Concerns/ConstraintsResolver.php
+++ b/src/Models/Concerns/ConstraintsResolver.php
@@ -7,6 +7,7 @@
 use Binarcode\LaravelMailator\Constraints\BeforeConstraint;
 use Binarcode\LaravelMailator\Constraints\DailyConstraint;
 use Binarcode\LaravelMailator\Constraints\Descriptionable;
+use Binarcode\LaravelMailator\Constraints\HoursSchedulerCheckerConstraint;
 use Binarcode\LaravelMailator\Constraints\ManualConstraint;
 use Binarcode\LaravelMailator\Constraints\ManyConstraint;
 use Binarcode\LaravelMailator\Constraints\NeverConstraint;
@@ -34,6 +35,7 @@ public function configurationsPasses(): bool
             ManyConstraint::class,
             DailyConstraint::class,
             WeeklyConstraint::class,
+            HoursSchedulerCheckerConstraint::class,
         ])
             ->map(fn ($class) => app($class))
             ->every(fn (SendScheduleConstraint $event) => $event->canSend($this, $this->logs));
@@ -49,7 +51,10 @@ public function eventsPasses(): bool
         return collect($this->constraints)
                 ->map(fn (string $event) => unserialize($event))
                 ->filter(fn ($event) => is_subclass_of($event, SendScheduleConstraint::class))
-                ->filter(fn (SendScheduleConstraint $event) => $event->canSend($this, $this->logs))->count() === collect($this->constraints)->count();
+                ->filter(fn (SendScheduleConstraint $event) => $event->canSend(
+                    $this,
+                    $this->logs
+                ))->count() === collect($this->constraints)->count();
     }
 
     public function constraintsDescriptions(): array
diff --git a/src/Models/MailatorSchedule.php b/src/Models/MailatorSchedule.php
index 97fad351df8f..e32140eddc73 100644
--- a/src/Models/MailatorSchedule.php
+++ b/src/Models/MailatorSchedule.php
@@ -53,6 +53,7 @@
  * @property Carbon $last_failed_at
  * @property string $failure_reason
  * @property Carbon $last_sent_at
+ * @property array|null $schedule_at_hours
  * @property Carbon $completed_at
  * @property string $frequency_option
  * @property-read Collection $logs
@@ -88,6 +89,7 @@ public function getTable()
     protected $casts = [
         'constraints' => 'array',
         'recipients' => 'array',
+        'schedule_at_hours' => 'array',
         'timestamp_target' => 'datetime',
         'last_failed_at' => 'datetime',
         'last_sent_at' => 'datetime',
@@ -244,6 +246,11 @@ public function isWeekly(): bool
         return $this->frequency_option === static::FREQUENCY_OPTIONS_WEEKLY;
     }
 
+    public function hasPrecision(): bool
+    {
+        return (bool) $this->schedule_at_hours;
+    }
+
     public function isAfter(): bool
     {
         return $this->time_frame_origin === static::TIME_FRAME_ORIGIN_AFTER;
@@ -307,6 +314,13 @@ public function days(int $number): self
         return $this;
     }
 
+    public function precision(array $scheduleAtHours): self
+    {
+        $this->schedule_at_hours = $scheduleAtHours;
+
+        return $this;
+    }
+
     public function weeks(int $number): static
     {
         $this->delay_minutes = $number * ConverterEnum::MINUTES_IN_WEEK;
@@ -368,7 +382,7 @@ public function shouldSend(): bool
             }
 
             return true;
-        } catch (Exception | Throwable $e) {
+        } catch (Exception|Throwable $e) {
             $this->markAsFailed($e->getMessage());
 
             app(ResolveGarbageAction::class)->handle($this);
@@ -408,7 +422,7 @@ public function execute(bool $now = false): void
                     dispatch(new SendMailJob($this));
                 }
             }
-        } catch (Exception | Throwable $e) {
+        } catch (Exception|Throwable $e) {
             $this->markAsFailed($e->getMessage());
         }
     }
@@ -427,7 +441,7 @@ public function getMailable(): ?Mailable
     {
         try {
             return unserialize($this->mailable_class);
-        } catch (Throwable | TypeError $e) {
+        } catch (Throwable|TypeError $e) {
             $this->markAsFailed($e->getMessage());
         }
 
@@ -494,7 +508,7 @@ public function actionClass(Action $action): self
         return $this;
     }
 
-    public function tag(string | array $tag): self
+    public function tag(string|array $tag): self
     {
         if (is_array($tag)) {
             $tag = implode(',', $tag);
diff --git a/tests/Feature/Models/MailatorScheduleTest.php b/tests/Feature/Models/MailatorScheduleTest.php
index 6295b5738e10..d16811f40383 100644
--- a/tests/Feature/Models/MailatorScheduleTest.php
+++ b/tests/Feature/Models/MailatorScheduleTest.php
@@ -62,7 +62,7 @@ public function test_can_use_carbon_target_date_before(): void
 
         //        MailatorSchedule::run();
         //        Mail::assertNothingSent();
-
+      
         $this->travelTo(now()->addDays(6));
         MailatorSchedule::run();
 
diff --git a/tests/Feature/WithWeekendsConstraintTest.php b/tests/Feature/WithWeekendsConstraintTest.php
new file mode 100644
index 000000000000..d8b42b092774
--- /dev/null
+++ b/tests/Feature/WithWeekendsConstraintTest.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace Binarcode\LaravelMailator\Tests\Feature;
+
+use Binarcode\LaravelMailator\Models\MailatorSchedule;
+use Binarcode\LaravelMailator\Tests\Fixtures\InvoiceReminderMailable;
+use Binarcode\LaravelMailator\Tests\TestCase;
+use Carbon\Carbon;
+use Illuminate\Support\Facades\Mail;
+use Spatie\TestTime\TestTime;
+
+class WithWeekendsConstraintTest extends TestCase
+{
+    public function test_can_send_mail_with_precision_at_the_given_hour(): void
+    {
+        Mail::fake();
+        Mail::assertNothingSent();
+
+        MailatorSchedule::init('Invoice reminder.')
+            ->recipients([
+                'zoo@bar.com',
+            ])
+            ->mailable(
+                (new InvoiceReminderMailable())->to('foo@bar.com')
+            )
+            ->precision([5])
+            ->save();
+
+        MailatorSchedule::run();
+        Mail::assertNotSent(InvoiceReminderMailable::class);
+
+        $this->travelTo(Carbon::parse('05:00:00'));
+        MailatorSchedule::run();
+
+        Mail::assertSent(InvoiceReminderMailable::class);
+
+        $this->travelTo(Carbon::parse('06:00:00'));
+
+        MailatorSchedule::run();
+
+        Mail::assertSent(InvoiceReminderMailable::class, 1);
+    }
+
+    public function test_can_set_precision_in_interval(): void
+    {
+        TestTime::freeze();
+
+        Mail::fake();
+        Mail::assertNothingSent();
+
+        MailatorSchedule::init('Invoice reminder.')
+            ->recipients([
+                'zoo@bar.com',
+            ])
+            ->mailable(
+                (new InvoiceReminderMailable())->to('foo@bar.com')
+            )
+            ->many()
+            ->precision([1, 2])
+            ->save();
+
+        $this->travelTo(Carbon::parse('12:00:00'));
+        MailatorSchedule::run();
+        Mail::assertNotSent(InvoiceReminderMailable::class);
+
+        $this->travelTo(Carbon::parse('01:00:00'));
+        MailatorSchedule::run();
+        Mail::assertSent(InvoiceReminderMailable::class);
+
+        $this->travelTo(Carbon::parse('02:59:59'));
+        MailatorSchedule::run();
+        Mail::assertSent(InvoiceReminderMailable::class);
+
+        $this->travelTo(Carbon::parse('03:00:00'));
+        MailatorSchedule::run();
+
+        Mail::assertSent(InvoiceReminderMailable::class, 2);
+    }
+}
diff --git a/tests/TestCase.php b/tests/TestCase.php
index ad72a6934f26..33f20daff98c 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -6,7 +6,6 @@
 use Illuminate\Contracts\View\Factory;
 use Mockery as m;
 use Orchestra\Testbench\TestCase as Orchestra;
-use Swift_Mailer;
 
 class TestCase extends Orchestra
 {

From 83db24f09ca930545e87f5552d1b74275f82fbc1 Mon Sep 17 00:00:00 2001
From: Hossain Mohammad Shahidullah Jaber
 <55241455+JaberWiki@users.noreply.github.com>
Date: Thu, 22 Feb 2024 16:06:35 +0600
Subject: [PATCH 27/34] fix: Update README.md for artisan command (#2577)

Updated artisan command for publishing config and migration files.
---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 571b039ba668..7bfa25694912 100644
--- a/README.md
+++ b/README.md
@@ -20,9 +20,9 @@ composer require binarcode/laravel-mailator
 
 ## Publish
 
-Publish migrations: `a vendor:publish --tag=mailator-migrations`
+Publish migrations: `php artisan vendor:publish --tag=mailator-migrations`
 
-Publish config: `a vendor:publish --tag=mailator-config`
+Publish config: `php artisan vendor:publish --tag=mailator-config`
 
 ## Usage
 

From 494c8375a6c15a71b4cb9df37f3215121967f268 Mon Sep 17 00:00:00 2001
From: Papuc Vasile <32643188+CaReS0107@users.noreply.github.com>
Date: Thu, 22 Feb 2024 12:08:50 +0200
Subject: [PATCH 28/34] Prevent issue:  $instance must not be accessed before
 initialization (#2578)

* Prevent issue:  must not be accessed before initialization

* Fix styling

* Fix styling

---------

Co-authored-by: CaReS0107 <CaReS0107@users.noreply.github.com>
---
 src/SchedulerManager.php                      | 2 +-
 tests/Feature/Models/MailatorScheduleTest.php | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/SchedulerManager.php b/src/SchedulerManager.php
index af96cb01e7ae..2c9cca7f8208 100644
--- a/src/SchedulerManager.php
+++ b/src/SchedulerManager.php
@@ -24,7 +24,7 @@ public function run(): void
 
     public function __destruct()
     {
-        if (! $this->instance->wasRecentlyCreated) {
+        if ($this->instance && ! $this->instance->wasRecentlyCreated) {
             $this->instance->save();
         }
     }
diff --git a/tests/Feature/Models/MailatorScheduleTest.php b/tests/Feature/Models/MailatorScheduleTest.php
index d16811f40383..6295b5738e10 100644
--- a/tests/Feature/Models/MailatorScheduleTest.php
+++ b/tests/Feature/Models/MailatorScheduleTest.php
@@ -62,7 +62,7 @@ public function test_can_use_carbon_target_date_before(): void
 
         //        MailatorSchedule::run();
         //        Mail::assertNothingSent();
-      
+
         $this->travelTo(now()->addDays(6));
         MailatorSchedule::run();
 

From a7553a42216418f2975fd16bd6317e97cec0d801 Mon Sep 17 00:00:00 2001
From: Laravel Shift <shift@laravelshift.com>
Date: Wed, 5 Jun 2024 14:28:58 -0400
Subject: [PATCH 29/34] Laravel 11.x Compatibility (#2579)

* Bump dependencies for Laravel 11

* Update GitHub Actions for Laravel 11
---
 .github/workflows/run-tests.yml | 86 ++++++++++++++++++---------------
 composer.json                   |  6 +--
 2 files changed, 50 insertions(+), 42 deletions(-)

diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index dc0089624aa3..3e4f87802467 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -1,43 +1,51 @@
 name: run-tests
 
-on: [push, pull_request]
+on:
+  - push
+  - pull_request
 
 jobs:
-    test:
-        runs-on: ${{ matrix.os }}
-        strategy:
-            fail-fast: true
-            matrix:
-                os: [ubuntu-latest, windows-latest]
-                php: [8.1]
-                laravel: [^10.0]
-                stability: [prefer-stable]
-                include:
-                    - laravel: ^10.0
-                      testbench: ^8.0
-
-        name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
-
-        steps:
-            - name: Checkout code
-              uses: actions/checkout@v2
-
-            - name: Setup PHP
-              uses: shivammathur/setup-php@v2
-              with:
-                  php-version: ${{ matrix.php }}
-                  extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
-                  coverage: none
-
-            - name: Setup problem matchers
-              run: |
-                  echo "::add-matcher::${{ runner.tool_cache }}/php.json"
-                  echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
-
-            - name: Install dependencies
-              run: |
-                  composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
-                  composer update --${{ matrix.stability }} --prefer-dist --no-interaction
-
-            - name: Execute tests
-              run: ./vendor/bin/testbench package:test
+  test:
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      fail-fast: true
+      matrix:
+        os: [ubuntu-latest, windows-latest]
+        php: [8.1, '8.2']
+        laravel: ['11.0', ^10.0]
+        stability: [prefer-stable]
+        include:
+          - laravel: ^10.0
+            testbench: ^8.0
+          - laravel: '11.0'
+            testbench: ^9.0
+        exclude:
+          - laravel: '11.0'
+            php: 8.1
+
+    name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v2
+
+      - name: Setup PHP
+        uses: shivammathur/setup-php@v2
+        with:
+          php-version: ${{ matrix.php }}
+          extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
+          coverage: none
+
+      - name: Setup problem matchers
+        run: |
+          echo "::add-matcher::${{ runner.tool_cache }}/php.json"
+          echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
+
+      - name: Install dependencies
+        run: |
+          composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
+          composer update --${{ matrix.stability }} --prefer-dist --no-interaction
+
+      - name: Execute tests
+        run: ./vendor/bin/testbench package:test
diff --git a/composer.json b/composer.json
index b961a953899b..5c5d4908c328 100644
--- a/composer.json
+++ b/composer.json
@@ -18,14 +18,14 @@
     ],
     "require": {
         "php": "^8.0|^8.1",
-        "illuminate/support": "^9.0|^10.0",
+        "illuminate/support": "^9.0|^10.0|^11.0",
         "opis/closure": "^3.6"
     },
     "require-dev": {
         "brianium/paratest": "^6.2|^7.0.6",
-        "nunomaduro/collision": "^5.3|^6.1|^7.0",
+        "nunomaduro/collision": "^5.3|^6.1|^7.0|^8.0",
         "nunomaduro/larastan": "^2.0",
-        "orchestra/testbench": "^8.0",
+        "orchestra/testbench": "^8.0|^9.0",
         "phpstan/extension-installer": "^1.1",
         "phpunit/phpunit": "^10.0",
         "spatie/laravel-ray": "^1.9",

From 83d589b04cd6db073080f2079faeabe0b89e7147 Mon Sep 17 00:00:00 2001
From: Arthur Kirkosa <arthur.kirkosa@me.com>
Date: Wed, 3 Jul 2024 10:18:08 +0300
Subject: [PATCH 30/34] Support PHP 8.2+ (#2586)

* Support PHP 8.2+

* Changes

* Fix PHP Stan

* Set connection to sync

* Fix styling

* Fix PHP Stan Install

---------

Co-authored-by: arthurkirkosa <arthurkirkosa@users.noreply.github.com>
---
 .github/workflows/php-cs-fixer.yml            |  2 +-
 .github/workflows/phpstan.yml                 |  4 +--
 .github/workflows/release.yml                 |  3 +-
 .github/workflows/run-tests.yml               | 30 +++++++++----------
 .github/workflows/update-changelog.yml        |  2 +-
 composer.json                                 | 10 +++----
 .../Commands/PruneMailatorLogsCommand.php     |  6 +++-
 .../Commands/PruneMailatorScheduleCommand.php |  7 ++++-
 src/Constraints/AfterConstraint.php           | 27 +++++++++++++----
 src/Constraints/BeforeConstraint.php          | 27 ++++++++++++-----
 src/LaravelMailatorServiceProvider.php        |  2 +-
 src/Models/Concerns/WithPrune.php             |  7 +++--
 .../Concerns/ReplaceModelAttributes.php       |  2 +-
 src/Replacers/ModelAttributesReplacer.php     |  2 +-
 src/Support/WithMailTemplate.php              |  2 +-
 tests/TestCase.php                            |  3 ++
 .../2017_10_10_000000_create_jobs_table.php   | 20 +++++++++++++
 .../2017_10_10_000000_create_posts_table.php  | 16 ++--------
 .../2017_10_10_000000_create_users_table.php  | 19 +++---------
 19 files changed, 117 insertions(+), 74 deletions(-)
 create mode 100644 tests/database/migrations/2017_10_10_000000_create_jobs_table.php

diff --git a/.github/workflows/php-cs-fixer.yml b/.github/workflows/php-cs-fixer.yml
index 62f6106ac43c..f0beab368979 100644
--- a/.github/workflows/php-cs-fixer.yml
+++ b/.github/workflows/php-cs-fixer.yml
@@ -8,7 +8,7 @@ jobs:
 
         steps:
             - name: Checkout code
-              uses: actions/checkout@v2
+              uses: actions/checkout@v4
               with:
                   ref: ${{ github.head_ref }}
 
diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml
index 0323ffc4fb6e..122a5bd2870f 100644
--- a/.github/workflows/phpstan.yml
+++ b/.github/workflows/phpstan.yml
@@ -11,12 +11,12 @@ jobs:
         name: phpstan
         runs-on: ubuntu-latest
         steps:
-            - uses: actions/checkout@v2
+            - uses: actions/checkout@v4
 
             - name: Setup PHP
               uses: shivammathur/setup-php@v2
               with:
-                  php-version: '8.1'
+                  php-version: '8.2'
                   coverage: none
 
             - name: Install composer dependencies
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index e1d83a2067a5..3ea054473c93 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -13,7 +13,8 @@ jobs:
         if: github.event.pull_request.merged == true
         runs-on: "ubuntu-latest"
         steps:
-            - uses: actions/checkout@v2
+            - uses: actions/checkout@v4
+
             - uses: ncipollo/release-action@v1
               with:
                   name: ${{ github.event.pull_request.title }}
diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index 3e4f87802467..b3ca5a568d86 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -1,34 +1,28 @@
 name: run-tests
 
-on:
-  - push
-  - pull_request
+on: [push, pull_request]
 
 jobs:
   test:
     runs-on: ${{ matrix.os }}
-
     strategy:
       fail-fast: true
       matrix:
         os: [ubuntu-latest, windows-latest]
-        php: [8.1, '8.2']
-        laravel: ['11.0', ^10.0]
+        php: [8.3, 8.2]
+        laravel: [10.*, 11.*]
         stability: [prefer-stable]
         include:
-          - laravel: ^10.0
-            testbench: ^8.0
-          - laravel: '11.0'
-            testbench: ^9.0
-        exclude:
-          - laravel: '11.0'
-            php: 8.1
+          - laravel: 10.*
+            testbench: 8.*
+          - laravel: 11.*
+            testbench: 9.*
 
     name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
 
     steps:
       - name: Checkout code
-        uses: actions/checkout@v2
+        uses: actions/checkout@v4
 
       - name: Setup PHP
         uses: shivammathur/setup-php@v2
@@ -47,5 +41,11 @@ jobs:
           composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
           composer update --${{ matrix.stability }} --prefer-dist --no-interaction
 
+      - name: Clear Composer cache
+        run: composer clear-cache
+
+      - name: Wait for a few seconds
+        run: sleep 5
+
       - name: Execute tests
-        run: ./vendor/bin/testbench package:test
+        run: ./vendor/bin/testbench package:test --no-coverage
diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml
index 6c58564a807f..3bc87c45db4d 100644
--- a/.github/workflows/update-changelog.yml
+++ b/.github/workflows/update-changelog.yml
@@ -10,7 +10,7 @@ jobs:
 
         steps:
             - name: Checkout code
-              uses: actions/checkout@v2
+              uses: actions/checkout@v4
               with:
                   ref: main
 
diff --git a/composer.json b/composer.json
index 5c5d4908c328..54df3167e77f 100644
--- a/composer.json
+++ b/composer.json
@@ -17,17 +17,17 @@
         }
     ],
     "require": {
-        "php": "^8.0|^8.1",
-        "illuminate/support": "^9.0|^10.0|^11.0",
+        "php": "^8.2",
+        "illuminate/support": "^10.0|^11.0",
         "opis/closure": "^3.6"
     },
     "require-dev": {
-        "brianium/paratest": "^6.2|^7.0.6",
-        "nunomaduro/collision": "^5.3|^6.1|^7.0|^8.0",
+        "brianium/paratest": "^7.0.6",
+        "nunomaduro/collision": "^7.0|^8.0",
         "nunomaduro/larastan": "^2.0",
         "orchestra/testbench": "^8.0|^9.0",
         "phpstan/extension-installer": "^1.1",
-        "phpunit/phpunit": "^10.0",
+        "phpunit/phpunit": "^10.0|^11.0",
         "spatie/laravel-ray": "^1.9",
         "spatie/test-time": "^1.2"
     },
diff --git a/src/Console/Commands/PruneMailatorLogsCommand.php b/src/Console/Commands/PruneMailatorLogsCommand.php
index 074ab39cb02c..a8328df86636 100644
--- a/src/Console/Commands/PruneMailatorLogsCommand.php
+++ b/src/Console/Commands/PruneMailatorLogsCommand.php
@@ -13,6 +13,10 @@ class PruneMailatorLogsCommand extends Command
 
     public function handle(): void
     {
-        $this->info(MailatorLog::prune(now()->subDays((int)$this->option('days'))) . ' entries pruned.');
+        $this->info(
+            MailatorLog::prune(
+                now()->subDays((int)$this->option('days'))
+            ) . ' entries pruned.'
+        );
     }
 }
diff --git a/src/Console/Commands/PruneMailatorScheduleCommand.php b/src/Console/Commands/PruneMailatorScheduleCommand.php
index c3ce2ad2815e..1fb724406d78 100644
--- a/src/Console/Commands/PruneMailatorScheduleCommand.php
+++ b/src/Console/Commands/PruneMailatorScheduleCommand.php
@@ -13,6 +13,11 @@ class PruneMailatorScheduleCommand extends Command
 
     public function handle(): void
     {
-        $this->info(MailatorSchedule::prune(now()->subDays((int)$this->option('days'))) . ' entries pruned.');
+        $this->info(
+            MailatorSchedule::prune(
+                now()->subDays((int)$this->option('days')),
+                ['logs']
+            ) . ' entries pruned.'
+        );
     }
 }
diff --git a/src/Constraints/AfterConstraint.php b/src/Constraints/AfterConstraint.php
index cafcc999fd95..9305932ece99 100644
--- a/src/Constraints/AfterConstraint.php
+++ b/src/Constraints/AfterConstraint.php
@@ -22,9 +22,14 @@ public function canSend(MailatorSchedule $schedule, Collection $logs): bool
                 return false;
             }
 
+            $diff = (int) $schedule->timestamp_target->diffInDays(
+                now()->floorSeconds(),
+                absolute: true
+            );
+
             return $schedule->isOnce()
-                ? $schedule->timestamp_target->diffInDays(now()->floorSeconds()) === $schedule->toDays()
-                : $schedule->timestamp_target->diffInDays(now()->floorSeconds()) > $schedule->toDays();
+                ? $diff === $schedule->toDays()
+                : $diff > $schedule->toDays();
         }
 
         if ($schedule->toHours() > 0) {
@@ -32,19 +37,29 @@ public function canSend(MailatorSchedule $schedule, Collection $logs): bool
                 return false;
             }
 
+            $diff = (int) $schedule->timestamp_target->diffInHours(
+                now()->floorSeconds(),
+                absolute: true
+            );
+
             //till ends we should have at least toDays days
             return $schedule->isOnce()
-                ? $schedule->timestamp_target->diffInHours(now()->floorSeconds()) === $schedule->toHours()
-                : $schedule->timestamp_target->diffInHours(now()->floorSeconds()) > $schedule->toHours();
+                ? $diff === $schedule->toHours()
+                : $diff > $schedule->toHours();
         }
 
         if (now()->floorSeconds()->lte($schedule->timestampTarget()->addMinutes($schedule->delay_minutes))) {
             return false;
         }
 
+        $diff = (int) $schedule->timestamp_target->diffInHours(
+            now()->floorSeconds(),
+            absolute: true
+        );
+
         //till ends we should have at least toDays days
         return $schedule->isOnce()
-            ? $schedule->timestamp_target->diffInHours(now()->floorSeconds()) === $schedule->delay_minutes
-            : $schedule->timestamp_target->diffInHours(now()->floorSeconds()) > $schedule->delay_minutes;
+            ? $diff === $schedule->delay_minutes
+            : $diff > $schedule->delay_minutes;
     }
 }
diff --git a/src/Constraints/BeforeConstraint.php b/src/Constraints/BeforeConstraint.php
index ae10d7f5eded..60a9ce476d18 100644
--- a/src/Constraints/BeforeConstraint.php
+++ b/src/Constraints/BeforeConstraint.php
@@ -27,10 +27,15 @@ public function canSend(MailatorSchedule $schedule, Collection $logs): bool
                 return false;
             }
 
+            $diff = (int) $schedule->timestampTarget()->diffInDays(
+                now()->floorSeconds(),
+                absolute: true
+            );
+
             //till ends we should have at least toDays days
             return $schedule->isOnce()
-                ? $schedule->timestampTarget()->diffInDays(now()->floorSeconds()) === $schedule->toDays()
-                : $schedule->timestampTarget()->diffInDays(now()->floorSeconds()) < $schedule->toDays();
+                ? $diff === $schedule->toDays()
+                : $diff < $schedule->toDays();
         }
 
         if ($schedule->toHours() > 0) {
@@ -38,17 +43,25 @@ public function canSend(MailatorSchedule $schedule, Collection $logs): bool
                 return false;
             }
 
+            $diff = (int) $schedule->timestamp_target->diffInHours(
+                now()->floorSeconds(),
+                absolute: true
+            );
+
             //till ends we should have at least toHours days
             return $schedule->isOnce()
-                ? $schedule->timestamp_target->diffInHours(now()->floorSeconds()) === $schedule->toHours()
-                : $schedule->timestamp_target->diffInHours(now()->floorSeconds()) < $schedule->toHours();
+                ? $diff === $schedule->toHours()
+                : $diff < $schedule->toHours();
         }
 
-
+        $diff = (int) $schedule->timestampTarget()->diffInDays(
+            now()->floorSeconds(),
+            absolute: true
+        );
 
         //till ends we should have at least toDays days
         return $schedule->isOnce()
-            ? $schedule->timestampTarget()->diffInDays(now()->floorSeconds()) === $schedule->toDays()
-            : $schedule->timestampTarget()->diffInDays(now()->floorSeconds()) < $schedule->toDays();
+            ? $diff === $schedule->toDays()
+            : $diff < $schedule->toDays();
     }
 }
diff --git a/src/LaravelMailatorServiceProvider.php b/src/LaravelMailatorServiceProvider.php
index 0df291d12bd6..6735e93f8270 100644
--- a/src/LaravelMailatorServiceProvider.php
+++ b/src/LaravelMailatorServiceProvider.php
@@ -50,7 +50,7 @@ public function boot()
 
             if (! class_exists('CreateMailatorTables')) {
                 $this->publishes([
-                    __DIR__ . '/../database/migrations/create_mailator_tables.php.stub' => database_path('migrations/' . date('Y_m_d_His', now()->subMinute(1)->timestamp) . '_create_mailator_tables.php'),
+                    __DIR__ . '/../database/migrations/create_mailator_tables.php.stub' => database_path('migrations/' . date('Y_m_d_His', now()->subMinute()->timestamp) . '_create_mailator_tables.php'),
                 ], 'mailator-migrations');
             }
 
diff --git a/src/Models/Concerns/WithPrune.php b/src/Models/Concerns/WithPrune.php
index a66a4581420e..1bc66355402c 100644
--- a/src/Models/Concerns/WithPrune.php
+++ b/src/Models/Concerns/WithPrune.php
@@ -10,10 +10,13 @@
  */
 trait WithPrune
 {
-    public static function prune(DateTimeInterface $before)
+    public static function prune(DateTimeInterface $before, array $with = [])
     {
         $query = static::query()
-            ->with('logs')
+            ->when(
+                $with !== [],
+                fn ($query) => $query->with($with)
+            )
             ->where('created_at', '<', $before);
 
         $totalDeleted = 0;
diff --git a/src/Replacers/Concerns/ReplaceModelAttributes.php b/src/Replacers/Concerns/ReplaceModelAttributes.php
index 841030a57602..a7e5d8b41138 100644
--- a/src/Replacers/Concerns/ReplaceModelAttributes.php
+++ b/src/Replacers/Concerns/ReplaceModelAttributes.php
@@ -30,7 +30,7 @@ public function replaceModelAttributes(string $text, string $replaceText, Model
                     ?? '';
             }, $model);
 
-            return $replace ?? $match;
+            return $replace ?: $match;
         }, $text);
     }
 }
diff --git a/src/Replacers/ModelAttributesReplacer.php b/src/Replacers/ModelAttributesReplacer.php
index 301c1ad49298..58535b0328c3 100644
--- a/src/Replacers/ModelAttributesReplacer.php
+++ b/src/Replacers/ModelAttributesReplacer.php
@@ -10,7 +10,7 @@ class ModelAttributesReplacer implements Replacer
 {
     use ReplaceModelAttributes;
 
-    /** * @var Model */
+    /** @var Model */
     protected $model;
 
     public function replace(string $html, MailTemplateable $template): string
diff --git a/src/Support/WithMailTemplate.php b/src/Support/WithMailTemplate.php
index c030d5415eb4..ca8d8703d559 100644
--- a/src/Support/WithMailTemplate.php
+++ b/src/Support/WithMailTemplate.php
@@ -25,7 +25,7 @@ public function template(MailTemplateable $template)
 
         $this->template = $template;
 
-        /** * @var PersonalizeMailAction $replacerAction */
+        /** @var PersonalizeMailAction $replacerAction */
         $replacerAction = app(PersonalizeMailAction::class);
 
         // replace placehlders
diff --git a/tests/TestCase.php b/tests/TestCase.php
index 33f20daff98c..fc2d5be05c5b 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -37,6 +37,8 @@ protected function getPackageProviders($app)
 
     protected function getEnvironmentSetUp($app)
     {
+        $app['config']->set('queue.default', 'sync');
+
         $app['config']->set('database.default', 'sqlite');
 
         $app['config']->set('database.connections.sqlite', [
@@ -45,6 +47,7 @@ protected function getEnvironmentSetUp($app)
             'prefix' => '',
         ]);
 
+
         include_once __DIR__.'/../database/migrations/create_mailator_tables.php.stub';
         (new \CreateMailatorTables())->up();
     }
diff --git a/tests/database/migrations/2017_10_10_000000_create_jobs_table.php b/tests/database/migrations/2017_10_10_000000_create_jobs_table.php
new file mode 100644
index 000000000000..fe16e3de11ea
--- /dev/null
+++ b/tests/database/migrations/2017_10_10_000000_create_jobs_table.php
@@ -0,0 +1,20 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class() extends Migration {
+    public function up(): void
+    {
+        Schema::create('jobs', function (Blueprint $table): void {
+            $table->id();
+            $table->string('queue')->index();
+            $table->longText('payload');
+            $table->unsignedTinyInteger('attempts');
+            $table->unsignedInteger('reserved_at')->nullable();
+            $table->unsignedInteger('available_at');
+            $table->unsignedInteger('created_at');
+        });
+    }
+};
diff --git a/tests/database/migrations/2017_10_10_000000_create_posts_table.php b/tests/database/migrations/2017_10_10_000000_create_posts_table.php
index 33d6e1c23b87..653ba2a1d656 100644
--- a/tests/database/migrations/2017_10_10_000000_create_posts_table.php
+++ b/tests/database/migrations/2017_10_10_000000_create_posts_table.php
@@ -4,13 +4,8 @@
 use Illuminate\Database\Schema\Blueprint;
 use Illuminate\Support\Facades\Schema;
 
-return new class extends Migration {
-    /**
-     * Run the migrations.
-     *
-     * @return void
-     */
-    public function up()
+return new class() extends Migration {
+    public function up(): void
     {
         Schema::create('posts', function (Blueprint $table) {
             $table->increments('id');
@@ -20,12 +15,7 @@ public function up()
         });
     }
 
-    /**
-     * Reverse the migrations.
-     *
-     * @return void
-     */
-    public function down()
+    public function down(): void
     {
         Schema::dropIfExists('posts');
     }
diff --git a/tests/database/migrations/2017_10_10_000000_create_users_table.php b/tests/database/migrations/2017_10_10_000000_create_users_table.php
index 97e7b2bb36ce..2a5bc24e7ecc 100644
--- a/tests/database/migrations/2017_10_10_000000_create_users_table.php
+++ b/tests/database/migrations/2017_10_10_000000_create_users_table.php
@@ -4,14 +4,8 @@
 use Illuminate\Database\Schema\Blueprint;
 use Illuminate\Support\Facades\Schema;
 
-class CreateUsersTable extends Migration
-{
-    /**
-     * Run the migrations.
-     *
-     * @return void
-     */
-    public function up()
+return new class() extends Migration {
+    public function up(): void
     {
         Schema::create('users', function (Blueprint $table) {
             $table->increments('id');
@@ -24,13 +18,8 @@ public function up()
         });
     }
 
-    /**
-     * Reverse the migrations.
-     *
-     * @return void
-     */
-    public function down()
+    public function down(): void
     {
         Schema::dropIfExists('users');
     }
-}
+};

From 96795fef887ccf8aff7dfa985517f1149116e43c Mon Sep 17 00:00:00 2001
From: Lupacescu Eduard <eduard.lupacescu@binarcode.com>
Date: Wed, 3 Jul 2024 10:24:59 +0300
Subject: [PATCH 31/34] fix: fixing minutes in days constant (#2587)

---
 src/Support/ConverterEnum.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Support/ConverterEnum.php b/src/Support/ConverterEnum.php
index f23aecc2a99b..4c88ab11bfea 100644
--- a/src/Support/ConverterEnum.php
+++ b/src/Support/ConverterEnum.php
@@ -5,7 +5,7 @@
 class ConverterEnum
 {
     public const MINUTES_IN_HOUR = 60;
-    public const MINUTES_IN_DAY = 60 * 60;
+    public const MINUTES_IN_DAY = 60 * 24;
     public const MINUTES_IN_WEEK = 168 * 60;
     public const HOURS_IN_DAY = 24;
     public const HOURS_IN_WEEK = 168;

From fc817808254a9334bcb98f1dd25c0900697cdafe Mon Sep 17 00:00:00 2001
From: Eduard Lupacescu <lupacescueduard@gmail.com>
Date: Mon, 2 Dec 2024 20:31:15 +0200
Subject: [PATCH 32/34] Refactor ReplaceModelAttributes to handle array values
 efficiently

---
 src/Replacers/Concerns/ReplaceModelAttributes.php | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/src/Replacers/Concerns/ReplaceModelAttributes.php b/src/Replacers/Concerns/ReplaceModelAttributes.php
index a7e5d8b41138..b3d07c6bf3b4 100644
--- a/src/Replacers/Concerns/ReplaceModelAttributes.php
+++ b/src/Replacers/Concerns/ReplaceModelAttributes.php
@@ -19,18 +19,20 @@ trait ReplaceModelAttributes
 {
     public function replaceModelAttributes(string $text, string $replaceText, Model $model)
     {
-        return preg_replace_callback('/::' . $replaceText . '::/', function ($match) use ($model) {
+        return preg_replace_callback('/::'.$replaceText.'::/', function ($match) use ($model) {
             $parts = collect(explode('.', $match[0] ?? ''));
 
             $replace = $parts->reduce(function ($value, $part) {
                 $part = Str::between($part, '::', '::');
 
-                return $value->$part
-                    ?? $value[$part]
-                    ?? '';
+                return $value->$part ?? $value[$part] ?? '';
             }, $model);
 
-            return $replace ?: $match;
+            if (is_array($replace)) {
+                return implode(', ', $replace);
+            }
+
+            return $replace ?: $match[0];
         }, $text);
     }
 }

From 7477a342f472396a28a5007d521bdca5e782139c Mon Sep 17 00:00:00 2001
From: Eduard Lupacescu <lupacescueduard@gmail.com>
Date: Wed, 22 Jan 2025 08:37:38 +0200
Subject: [PATCH 33/34] fix: fixing instance not initialized

---
 src/SchedulerManager.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/SchedulerManager.php b/src/SchedulerManager.php
index 2c9cca7f8208..d86898b4e35e 100644
--- a/src/SchedulerManager.php
+++ b/src/SchedulerManager.php
@@ -24,7 +24,7 @@ public function run(): void
 
     public function __destruct()
     {
-        if ($this->instance && ! $this->instance->wasRecentlyCreated) {
+        if (isset($this->instance) && $this->instance && ! $this->instance->wasRecentlyCreated) {
             $this->instance->save();
         }
     }

From f6d12a545204f3eb37d3e428bf94ff070bd948e0 Mon Sep 17 00:00:00 2001
From: Victor Malai <malai9696@gmail.com>
Date: Mon, 27 Jan 2025 12:34:07 +0200
Subject: [PATCH 34/34] fix(postgresql): enforce public properties for
 PostgreSQL serialization (#2589)

* fix(postgresql): enforce public properties to fix postgreSQL serialization

* Fix styling

---------

Co-authored-by: maloun96 <maloun96@users.noreply.github.com>
---
 config/mailator.php                           | 10 +++
 src/Models/MailatorSchedule.php               | 21 +++++
 .../Feature/AllowNonPublicPropertiesTest.php  | 84 +++++++++++++++++++
 tests/Fixtures/PrivatePropertyMailable.php    | 24 ++++++
 tests/Fixtures/ProtectedPropertyMailable.php  | 24 ++++++
 tests/Fixtures/PublicPropertyMailable.php     | 24 ++++++
 6 files changed, 187 insertions(+)
 create mode 100644 tests/Feature/AllowNonPublicPropertiesTest.php
 create mode 100644 tests/Fixtures/PrivatePropertyMailable.php
 create mode 100644 tests/Fixtures/ProtectedPropertyMailable.php
 create mode 100644 tests/Fixtures/PublicPropertyMailable.php

diff --git a/config/mailator.php b/config/mailator.php
index dab7be5440a5..d9282568f949 100644
--- a/config/mailator.php
+++ b/config/mailator.php
@@ -63,4 +63,14 @@
             Binarcode\LaravelMailator\Replacers\SampleReplacer::class,
         ],
     ],
+
+    'serialization' => [
+        /*
+        > Controls constructor property accessibility in mailable objects for PostgreSQL compatibility.
+        > When set to false, allows private/protected properties. When true, enforces
+        > public properties only to prevent PostgreSQL serialization errors caused by
+        > null bytes (\x00) in non-public properties.
+        */
+        'enforce_public_properties' => false,
+    ]
 ];
diff --git a/src/Models/MailatorSchedule.php b/src/Models/MailatorSchedule.php
index e32140eddc73..f8d7d59ca38f 100644
--- a/src/Models/MailatorSchedule.php
+++ b/src/Models/MailatorSchedule.php
@@ -30,6 +30,8 @@
 use Illuminate\Support\Facades\Validator;
 use Illuminate\Support\Str;
 use Opis\Closure\SerializableClosure;
+use ReflectionClass;
+use RuntimeException;
 use Throwable;
 use TypeError;
 
@@ -113,6 +115,10 @@ public static function init(string $name): self
 
     public function mailable(Mailable $mailable): self
     {
+        if (config('mailator.serialization.enforce_public_properties') && $this->hasNonPublicConstructorProps($mailable)) {
+            throw new RuntimeException('Mailable contains non-public constructor properties which cannot be safely serialized');
+        }
+
         if ($mailable instanceof Constraintable) {
             collect($mailable->constraints())
                 ->filter(fn ($constraint) => $constraint instanceof SendScheduleConstraint)
@@ -617,4 +623,19 @@ public function save(array $options = [])
 
         return parent::save($options);
     }
+
+    private function hasNonPublicConstructorProps(Mailable $mailable): bool
+    {
+        $reflection = new ReflectionClass($mailable);
+        $constructor = $reflection->getConstructor();
+
+        if (! $constructor) {
+            return false;
+        }
+
+        return collect($constructor->getParameters())
+            ->filter(fn ($param) => $reflection->getProperty($param->getName())->isPrivate()
+                || $reflection->getProperty($param->getName())->isProtected())
+            ->isNotEmpty();
+    }
 }
diff --git a/tests/Feature/AllowNonPublicPropertiesTest.php b/tests/Feature/AllowNonPublicPropertiesTest.php
new file mode 100644
index 000000000000..b964cf4e9c41
--- /dev/null
+++ b/tests/Feature/AllowNonPublicPropertiesTest.php
@@ -0,0 +1,84 @@
+<?php
+
+namespace Binarcode\LaravelMailator\Tests\Feature;
+
+use Binarcode\LaravelMailator\Models\MailatorSchedule;
+use Binarcode\LaravelMailator\Tests\Fixtures\PrivatePropertyMailable;
+use Binarcode\LaravelMailator\Tests\Fixtures\ProtectedPropertyMailable;
+use Binarcode\LaravelMailator\Tests\Fixtures\PublicPropertyMailable;
+use Binarcode\LaravelMailator\Tests\TestCase;
+use Illuminate\Support\Facades\Mail;
+use RuntimeException;
+
+class AllowNonPublicPropertiesTest extends TestCase
+{
+    protected function setUp(): void
+    {
+        parent::setUp();
+
+        Mail::fake();
+    }
+
+    public function test_can_send_email_with_private_property(): void
+    {
+        config()->set('mailator.serialization.enforce_public_properties', false);
+
+        MailatorSchedule::init('private')
+            ->mailable(new PrivatePropertyMailable('test'))
+            ->execute();
+
+        Mail::assertSent(PrivatePropertyMailable::class);
+    }
+
+    public function test_can_not_send_email_with_private_property(): void
+    {
+        config()->set('mailator.serialization.enforce_public_properties', true);
+
+        $this->expectException(RuntimeException::class);
+        $this->expectExceptionMessage('Mailable contains non-public constructor properties which cannot be safely serialized');
+
+        MailatorSchedule::init('private')
+            ->mailable(new PrivatePropertyMailable('test'))
+            ->execute();
+    }
+
+    public function test_can_send_email_with_protected_property(): void
+    {
+        config()->set('mailator.serialization.enforce_public_properties', false);
+
+        MailatorSchedule::init('protected')
+            ->mailable(new ProtectedPropertyMailable('test'))
+            ->execute();
+
+        Mail::assertSent(ProtectedPropertyMailable::class);
+    }
+
+    public function test_can_not_send_email_with_protected_property(): void
+    {
+        config()->set('mailator.serialization.enforce_public_properties', true);
+
+        $this->expectException(RuntimeException::class);
+        $this->expectExceptionMessage('Mailable contains non-public constructor properties which cannot be safely serialized');
+
+        MailatorSchedule::init('protected')
+            ->mailable(new ProtectedPropertyMailable('test'))
+            ->execute();
+    }
+
+    public function test_can_send_email_with_public_property(): void
+    {
+        config()->set('mailator.serialization.enforce_public_properties', true);
+
+        MailatorSchedule::init('protected')
+            ->mailable(new PublicPropertyMailable('test'))
+            ->execute();
+
+        Mail::assertSent(PublicPropertyMailable::class);
+
+        config()->set('mailator.serialization.enforce_public_properties', false);
+
+        MailatorSchedule::init('protected')
+            ->mailable(new PublicPropertyMailable('test'))
+            ->execute();
+    }
+}
diff --git a/tests/Fixtures/PrivatePropertyMailable.php b/tests/Fixtures/PrivatePropertyMailable.php
new file mode 100644
index 000000000000..71e626c9c723
--- /dev/null
+++ b/tests/Fixtures/PrivatePropertyMailable.php
@@ -0,0 +1,24 @@
+<?php
+
+
+namespace Binarcode\LaravelMailator\Tests\Fixtures;
+
+use Illuminate\Bus\Queueable;
+use Illuminate\Mail\Mailable;
+use Illuminate\Queue\SerializesModels;
+
+class PrivatePropertyMailable extends Mailable
+{
+    use Queueable;
+    use SerializesModels;
+
+    public function __construct(
+        private string $name
+    ) {
+    }
+
+    public function build()
+    {
+        return $this->view('laravel-mailator::mails.stub_invoice_reminder_view');
+    }
+}
diff --git a/tests/Fixtures/ProtectedPropertyMailable.php b/tests/Fixtures/ProtectedPropertyMailable.php
new file mode 100644
index 000000000000..18922eae6c49
--- /dev/null
+++ b/tests/Fixtures/ProtectedPropertyMailable.php
@@ -0,0 +1,24 @@
+<?php
+
+
+namespace Binarcode\LaravelMailator\Tests\Fixtures;
+
+use Illuminate\Bus\Queueable;
+use Illuminate\Mail\Mailable;
+use Illuminate\Queue\SerializesModels;
+
+class ProtectedPropertyMailable extends Mailable
+{
+    use Queueable;
+    use SerializesModels;
+
+    public function __construct(
+        protected string $name
+    ) {
+    }
+
+    public function build()
+    {
+        return $this->view('laravel-mailator::mails.stub_invoice_reminder_view');
+    }
+}
diff --git a/tests/Fixtures/PublicPropertyMailable.php b/tests/Fixtures/PublicPropertyMailable.php
new file mode 100644
index 000000000000..024d260a72ce
--- /dev/null
+++ b/tests/Fixtures/PublicPropertyMailable.php
@@ -0,0 +1,24 @@
+<?php
+
+
+namespace Binarcode\LaravelMailator\Tests\Fixtures;
+
+use Illuminate\Bus\Queueable;
+use Illuminate\Mail\Mailable;
+use Illuminate\Queue\SerializesModels;
+
+class PublicPropertyMailable extends Mailable
+{
+    use Queueable;
+    use SerializesModels;
+
+    public function __construct(
+        public string $name
+    ) {
+    }
+
+    public function build()
+    {
+        return $this->view('laravel-mailator::mails.stub_invoice_reminder_view');
+    }
+}