-# Package Documentation
-
-This package allows you to store and send e-mails using a database.
+# Introduction
-## Contribution
-
-The package is MIT licenced, meaning it's open source and you are free to copy or fork it and modify it any way you wish.
-
-We feel the package is currently feature complete, but feel free to send a pull request or help improve existing code.
+This package allows you to store and send e-mails using the database.
# Requirements
-This package requires Laravel 6.0 or higher.
-
-Please check the [Laravel support policy](https://laravel.com/docs/master/releases#support-policy) table for supported Laravel and PHP versions.
+This package requires Laravel 10 or 11.
# Installation
Require the package using composer.
-```bash
+```shell
composer require stackkit/laravel-database-emails
```
Publish the configuration files.
-```bash
-php artisan vendor:publish --tag=laravel-database-emails-config
+```shell
+php artisan vendor:publish --tag=database-emails-config
+php artisan vendor:publish --tag=database-emails-migrations
```
Create the database table required for this package.
-```bash
+```shell
php artisan migrate
```
Add the e-mail cronjob to your scheduler
```php
-command('email:send')->everyMinute()->withoutOverlapping(5);
@@ -65,68 +47,73 @@ protected function schedule(Schedule $schedule)
### Send an email
-```php
-label('welcome')
- ->recipient('john@doe.com')
- ->subject('This is a test')
- ->view('emails.welcome')
- ->variables([
- 'name' => 'John Doe',
+ ->content(fn (Content $content) => $content
+ ->view('tests::dummy')
+ ->with(['name' => 'John Doe'])
+ )
+ ->envelope(fn (Envelope $envelope) => $envelope
+ ->subject('Hello')
+ ->from('johndoe@example.com', 'John Doe')
+ ->to('janedoe@example.com', 'Jane Doe')
+ )
+ ->attachments([
+ Attachment::fromStorageDisk('s3', '/invoices/john-doe/march-2024.pdf'),
])
->send();
+])
```
-### Specify multiple recipients
+### Sending emails to users in your application
```php
-recipient([
- 'john@doe.com',
- 'jane@doe.com'
- ]);
+ ->user($user)
+ ->send();
```
-### CC and BCC
+By default, the `name` column will be used to set the recipient's name. If you wish to use something different, you should implement the `preferredEmailName` method in your model.
```php
-cc('john@doe.com')
- ->cc(['john@doe.com', 'jane@doe.com'])
- ->bcc('john@doe.com')
- ->bcc(['john@doe.com', 'jane@doe.com']);
+class User extends Model
+{
+ public function preferredEmailName(): string
+ {
+ return $this->first_name;
+ }
+}
```
-### Reply-To
+By default, the `email` column will be used to set the recipient's e-mail address. If you wish to use something different, you should implement the `preferredEmailAddress` method in your model.
```php
-replyTo(['john@doe.com', 'jane@doe.com']);
+class User extends Model
+{
+ public function preferredEmailAddress(): string
+ {
+ return $this->work_email;
+ }
+}
+```
-Email::compose()
- ->replyTo(new Address('john@doe.com', 'John Doe'));
+By default, the app locale will be used. If you wish to use something different, you should implement the `preferredEmailLocale` method in your model.
-Email::compose()
- ->replyTo([
- new Address('john@doe.com', 'John Doe'),
- new Address('jane@doe.com', 'Jane Doe'),
- ]);
+```php
+class User extends Model implements HasLocalePreference
+{
+ public function preferredLocale(): string
+ {
+ return $this->locale;
+ }
+}
```
### Using mailables
@@ -134,10 +121,6 @@ Email::compose()
You may also pass a mailable to the e-mail composer.
```php
-mailable(new OrderShipped())
->send();
@@ -145,35 +128,30 @@ Email::compose()
### Attachments
-```php
-attach('/path/to/file');
-```
+To start attaching files to your e-mails, you may use the `attachments` method like you normally would in Laravel.
+However, you will have to use this package's `Attachment` class.
-Or for in-memory attachments:
```php
-attachData('
Your order has shipped!
', 'order.html');
+ ->attachments([
+ Attachment::fromPath(__DIR__.'/files/pdf-sample.pdf'),
+ Attachment::fromPath(__DIR__.'/files/my-file.txt')->as('Test123 file'),
+ Attachment::fromStorageDisk('my-custom-disk', 'test.txt'),
+ ])
+ ->send();
```
-### Custom Sender
+> [!NOTE]
+> `Attachment::fromData()` and `Attachment::fromStorage()` are not supported as they work with raw data.
-```php
-from('john@doe.com', 'John Doe');
+ ->model(User::find(1));
```
### Scheduling
@@ -181,76 +159,75 @@ Email::compose()
You may schedule an e-mail by calling `later` instead of `send`. You must provide a Carbon instance or a strtotime valid date.
```php
-later('+2 hours');
```
-### Encryption (Optional)
+### Queueing e-mails
+
+> [!IMPORTANT]
+> When queueing mail using the `queue` function, it is no longer necessary to schedule the `email:send` command.
-If you wish to encrypt your e-mails, please enable the `encrypt` option in the configuration file. This is disabled by default. Encryption and decryption will be handled by Laravel's built-in encryption mechanism. Please note that by encrypting the e-mail it takes more disk space.
+```php
+Email::compose()->queue();
-```text
-Without encryption
+// On a specific connection
+Email::compose()->queue(connection: 'sqs');
-7 bytes (label)
-16 bytes (recipient)
-20 bytes (subject)
-48 bytes (view name)
-116 bytes (variables)
-1874 bytes (e-mail content)
-4 bytes (attempts, sending, failed, encrypted)
-57 bytes (created_at, updated_at, deleted_at)
-... x 10.000 rows = ± 21.55 MB
+// On a specific queue
+Email::compose()->queue(queue: 'email-queue');
-With encryption the table size is ± 50.58 MB.
+// Delay (send mail in 10 minutes)
+Email::compose()->queue(delay: now()->addMinutes(10));
```
+If you need more flexibility, you may also pass your own job class:
-### Queueing e-mails
+```php
+Email::compose()->queue(jobClass: CustomSendEmailJob::class);
+```
-**Important**: When queueing mail using the `queue` function, it is no longer necessary to schedule the `email:send` command. Please make sure it is removed from `app/Console/Kernel.php`.
+It could look like this:
```php
queue();
-
-// on a specific connection
-Email::compose()
- ->queue('sqs');
+namespace App\Jobs;
-// on a specific queue
-Email::compose()
- ->queue(null, 'email-queue');
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Stackkit\LaravelDatabaseEmails\SendEmailJob;
-// timeout (send mail in 10 minutes)
-Email::compose()
- ->queue(null, null, now()->addMinutes(10));
+class CustomSendEmailJob extends SendEmailJob implements ShouldQueue
+{
+ // Define custom retries, backoff, etc...
+}
```
-### Test mode (Optional)
+### Test mode
When enabled, all newly created e-mails will be sent to the specified test e-mail address. This is turned off by default.
+```dotenv
+DB_EMAILS_TESTING_ENABLED=true
+DB_EMAILS_TESTING_EMAIL=your-test-recipient@example.com
+```
+
### E-mails to send per minute
-To configure how many e-mails should be sent each command, please check the `limit` option. The default is `20` e-mails every command.
+To configure how many e-mails should be sent each command.
-### Send e-mails immediately (Optional)
+```dotenv
+DB_EMAILS_LIMIT=20
+```
+
+### Send e-mails immediately
Useful during development when Laravel Scheduler is not running
To enable, set the following environment variable:
-```
-LARAVEL_DATABASE_EMAILS_SEND_IMMEDIATELY=true
+```dotenv
+DB_EMAILS_IMMEDIATELY=true
```
### Pruning models
@@ -260,7 +237,7 @@ use Stackkit\LaravelDatabaseEmails\Email;
$schedule->command('model:prune', [
'--model' => [Email::class],
-])->everyMinute();
+])->daily();
```
By default, e-mails are pruned when they are older than 6 months.
diff --git a/UPGRADING.md b/UPGRADING.md
new file mode 100644
index 0000000..b38359d
--- /dev/null
+++ b/UPGRADING.md
@@ -0,0 +1,80 @@
+# From 6.x to 7.x
+
+7.x is a bigger change which cleans up parts of the code base and modernizes the package. That means there are a few high-impact changes.
+
+## Database changes (Impact: High)
+
+The way addresses are stored in the database has changed. Therefore, emails created in 6.x and below are incompatible.
+
+When you upgrade, the existing database table will be renamed to "emails_old" and a new table will be created.
+
+The table migration now needs to be published first. Please run this command:
+
+```shell
+php artisan vendor:publish --tag=database-emails-migrations
+```
+
+Then, run the migration:
+
+```shell
+php artisan migrate
+```
+
+## Environment variables, configurations (Impact: High)
+
+Environment variable names, as well as the config file name, have been shortened.
+
+Please publish the new configuration file:
+
+```shell
+php artisan vendor:publish --tag=database-emails-config
+```
+
+You can remove the old configuration file.
+
+Rename the following environments:
+
+- `LARAVEL_DATABASE_EMAILS_TESTING_ENABLED` → `DB_EMAILS_TESTING_ENABLED`
+- `LARAVEL_DATABASE_EMAILS_SEND_IMMEDIATELY` → `DB_EMAILS_SEND_IMMEDIATELY`
+
+The following environments are new:
+
+- `DB_EMAILS_ATTEMPTS`
+- `DB_EMAILS_TESTING_EMAIL`
+- `DB_EMAILS_LIMIT`
+- `DB_EMAILS_IMMEDIATELY`
+
+The following environments have been removed:
+
+- `LARAVEL_DATABASE_EMAILS_MANUAL_MIGRATIONS` because migrations are always published.
+
+## Creating emails (Impact: High)
+
+The way emails are composed has changed and now borrows a lot from Laravel's mailable.
+
+```php
+use Illuminate\Mail\Mailables\Content;
+use Stackkit\LaravelDatabaseEmails\Attachment;
+use Stackkit\LaravelDatabaseEmails\Email;
+use Illuminate\Mail\Mailables\Envelope;
+
+Email::compose()
+ ->content(fn (Content $content) => $content
+ ->view('tests::dummy')
+ ->with(['name' => 'John Doe'])
+ )
+ ->envelope(fn (Envelope $envelope) => $envelope
+ ->subject('Hello')
+ ->from('johndoe@example.com', 'John Doe')
+ ->to('janedoe@example.com', 'Jane Doe')
+ )
+ ->attachments([
+ Attachment::fromStorageDisk('s3', '/invoices/john-doe/march-2024.pdf'),
+ ])
+ ->send();
+])
+```
+
+## Encryption (Impact: moderate/low)
+
+E-mail encryption has been removed from the package.
diff --git a/composer.json b/composer.json
index ed7846f..0d16c4d 100644
--- a/composer.json
+++ b/composer.json
@@ -15,7 +15,10 @@
},
"autoload-dev": {
"psr-4": {
- "Tests\\": "tests/"
+ "Tests\\": "tests/",
+ "Workbench\\App\\": "workbench/app/",
+ "Workbench\\Database\\Factories\\": "workbench/database/factories/",
+ "Workbench\\Database\\Seeders\\": "workbench/database/seeders/"
}
},
"extra": {
@@ -27,24 +30,42 @@
},
"require": {
"ext-json": "*",
- "laravel/framework": "^10.0|^11.0"
+ "laravel/framework": "^10.0|^11.0",
+ "doctrine/dbal": "^3.8"
},
"require-dev": {
"mockery/mockery": "^1.2",
"orchestra/testbench": "^8.0|^9.0",
+ "nunomaduro/collision": "^7.0|^8.0",
"laravel/pint": "^1.14"
},
"minimum-stability": "dev",
"prefer-stable": true,
"scripts": {
"l11": [
- "composer update laravel/framework:11.* orchestra/testbench:9.* --with-all-dependencies"
+ "composer update laravel/framework:11.* orchestra/testbench:9.* nunomaduro/collision:8.* --with-all-dependencies"
],
"l10": [
- "composer update laravel/framework:10.* orchestra/testbench:8.* --with-all-dependencies"
+ "composer update laravel/framework:10.* orchestra/testbench:8.* nunomaduro/collision:7.* --with-all-dependencies"
],
"test": [
- "CI_DB_DRIVER=sqlite CI_DB_DATABASE=:memory: phpunit"
+ "testbench package:test"
+ ],
+ "post-autoload-dump": [
+ "@clear",
+ "@prepare"
+ ],
+ "clear": "@php vendor/bin/testbench package:purge-skeleton --ansi",
+ "prepare": "@php vendor/bin/testbench package:discover --ansi",
+ "build": "@php vendor/bin/testbench workbench:build --ansi",
+ "serve": [
+ "Composer\\Config::disableProcessTimeout",
+ "@build",
+ "@php vendor/bin/testbench serve"
+ ],
+ "lint": [
+ "@php vendor/bin/pint",
+ "@php vendor/bin/phpstan analyse"
]
}
}
diff --git a/config/laravel-database-emails.php b/config/database-emails.php
similarity index 60%
rename from config/laravel-database-emails.php
rename to config/database-emails.php
index 6c179ef..0d85003 100644
--- a/config/laravel-database-emails.php
+++ b/config/database-emails.php
@@ -13,19 +13,7 @@
|
*/
- 'attempts' => 3,
-
- /*
- |--------------------------------------------------------------------------
- | Encryption
- |--------------------------------------------------------------------------
- |
- | Here you may enable encryption for all e-mails. The e-mail will be encrypted according
- | your application's configuration (OpenSSL AES-256-CBC by default).
- |
- */
-
- 'encrypt' => false,
+ 'attempts' => env('DB_EMAILS_ATTEMPTS', 3),
/*
|--------------------------------------------------------------------------
@@ -40,9 +28,9 @@
'testing' => [
- 'email' => 'test@email.com',
+ 'email' => env('DB_EMAILS_TESTING_EMAIL'),
- 'enabled' => env('LARAVEL_DATABASE_EMAILS_TESTING_ENABLED', true),
+ 'enabled' => env('DB_EMAILS_TESTING_ENABLED', false),
],
@@ -57,7 +45,7 @@
|
*/
- 'limit' => 20,
+ 'limit' => env('DB_EMAILS_LIMIT', 20),
/*
|--------------------------------------------------------------------------
@@ -70,18 +58,5 @@
|
*/
- 'immediately' => env('LARAVEL_DATABASE_EMAILS_SEND_IMMEDIATELY', false),
-
- /*
- |--------------------------------------------------------------------------
- | Manual migrations
- |--------------------------------------------------------------------------
- |
- | This option allows you to use:
- | `php artisan vendor:publish --tag=laravel-database-emails-migrations` to push migrations
- | to your app's folder, so you're free to modify before migrating.
- |
- */
-
- 'manual_migrations' => (bool) env('LARAVEL_DATABASE_EMAILS_MANUAL_MIGRATIONS', false),
+ 'immediately' => env('DB_EMAILS_IMMEDIATELY', false),
];
diff --git a/database/migrations/2017_12_14_151421_add_attachments_to_emails_table.php b/database/migrations/2017_12_14_151421_add_attachments_to_emails_table.php
deleted file mode 100644
index dd49ce6..0000000
--- a/database/migrations/2017_12_14_151421_add_attachments_to_emails_table.php
+++ /dev/null
@@ -1,34 +0,0 @@
-binary('attachments')->after('body')->nullable();
- });
- }
-
- /**
- * Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- //
- }
-}
diff --git a/database/migrations/2017_12_22_114011_add_from_to_emails_table.php b/database/migrations/2017_12_22_114011_add_from_to_emails_table.php
deleted file mode 100644
index de25f07..0000000
--- a/database/migrations/2017_12_22_114011_add_from_to_emails_table.php
+++ /dev/null
@@ -1,34 +0,0 @@
-binary('from')->after('body')->nullable();
- });
- }
-
- /**
- * Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- //
- }
-}
diff --git a/database/migrations/2021_12_02_21000_add_queued_at_to_emails_table.php b/database/migrations/2021_12_02_21000_add_queued_at_to_emails_table.php
deleted file mode 100644
index 080bf35..0000000
--- a/database/migrations/2021_12_02_21000_add_queued_at_to_emails_table.php
+++ /dev/null
@@ -1,34 +0,0 @@
-timestamp('queued_at')->after('encrypted')->nullable();
- });
- }
-
- /**
- * Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- //
- }
-}
diff --git a/database/migrations/2023_12_28_140000_add_reply_to_to_emails_table.php b/database/migrations/2023_12_28_140000_add_reply_to_to_emails_table.php
deleted file mode 100644
index 09d4081..0000000
--- a/database/migrations/2023_12_28_140000_add_reply_to_to_emails_table.php
+++ /dev/null
@@ -1,30 +0,0 @@
-binary('reply_to')->nullable()->after('bcc');
- });
- }
-
- /**
- * Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- //
- }
-}
diff --git a/database/migrations/2017_12_14_151403_create_emails_table.php b/database/migrations/2024_03_16_151608_create_new_table.php
similarity index 58%
rename from database/migrations/2017_12_14_151403_create_emails_table.php
rename to database/migrations/2024_03_16_151608_create_new_table.php
index dc49d1f..5c0424a 100644
--- a/database/migrations/2017_12_14_151403_create_emails_table.php
+++ b/database/migrations/2024_03_16_151608_create_new_table.php
@@ -1,10 +1,10 @@
increments('id');
$table->string('label')->nullable();
- $table->binary('recipient');
- $table->binary('cc')->nullable();
- $table->binary('bcc')->nullable();
- $table->binary('subject');
- $table->string('view', 255);
- $table->binary('variables')->nullable();
- $table->binary('body');
+ $table->json('recipient');
+ $table->json('cc')->nullable();
+ $table->json('bcc')->nullable();
+ $table->string('subject');
+ $table->string('view');
+ $table->json('variables')->nullable();
+ $table->text('body');
$table->integer('attempts')->default(0);
$table->boolean('sending')->default(0);
$table->boolean('failed')->default(0);
$table->text('error')->nullable();
- $table->boolean('encrypted')->default(0);
+ $table->json('attachments')->nullable();
+ $table->json('from')->nullable();
+ $table->nullableMorphs('model');
+ $table->json('reply_to')->nullable();
+ $table->timestamp('queued_at')->nullable();
$table->timestamp('scheduled_at')->nullable();
- $table->timestamp('sent_at')->nullable();
+ $table->timestamp('sent_at')->nullable()->index();
$table->timestamp('delivered_at')->nullable();
$table->timestamps();
$table->softDeletes();
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..972092b
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,18 @@
+services:
+ mysql:
+ image: mysql:8
+ ports:
+ - '${MYSQL_PORT:-3307}:3306'
+ environment:
+ MYSQL_USER: 'test'
+ MYSQL_PASSWORD: 'test'
+ MYSQL_DATABASE: 'test'
+ MYSQL_RANDOM_ROOT_PASSWORD: true
+ pgsql:
+ image: postgres:14
+ ports:
+ - '${POSTGRES_PORT:-5432}:5432'
+ environment:
+ POSTGRES_USER: 'test'
+ POSTGRES_PASSWORD: 'test'
+ POSTGRES_DB: 'test'
diff --git a/logo.png b/logo.png
deleted file mode 100644
index 69d1e49..0000000
Binary files a/logo.png and /dev/null differ
diff --git a/phpunit.xml b/phpunit.xml
index eb3301d..a1ec749 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,10 +1,5 @@
-
-
-
- src/
-
-
+./tests/
@@ -19,4 +14,9 @@
+
+
+ src/
+
+
diff --git a/src/Attachment.php b/src/Attachment.php
new file mode 100644
index 0000000..dc4d016
--- /dev/null
+++ b/src/Attachment.php
@@ -0,0 +1,63 @@
+as = $name;
+
+ return $this;
+ }
+
+ public function withMime(string $mime): self
+ {
+ $this->mime = $mime;
+
+ return $this;
+ }
+
+ public function toArray(): array
+ {
+ return [
+ 'path' => $this->path,
+ 'disk' => $this->disk,
+ 'as' => $this->as,
+ 'mime' => $this->mime,
+ ];
+ }
+}
diff --git a/src/Config.php b/src/Config.php
index 28640c4..689eff4 100644
--- a/src/Config.php
+++ b/src/Config.php
@@ -8,61 +8,41 @@ class Config
{
/**
* Get the maximum number of times an e-mail may be attempted to be sent.
- *
- * @return int
*/
public static function maxAttemptCount(): int
{
- return max(config('laravel-database-emails.attempts', 1), 3);
- }
-
- /**
- * Determine if newly created e-mails should be encrypted.
- *
- * @return bool
- */
- public static function encryptEmails(): bool
- {
- return config('laravel-database-emails.encrypt', false);
+ return max(config('database-emails.attempts', 1), 3);
}
/**
* Determine if newly created e-mails should be sent to the test e-mail address.
- *
- * @return bool
*/
public static function testing(): bool
{
- return (bool) config('laravel-database-emails.testing.enabled', false);
+ return (bool) config('database-emails.testing.enabled', false);
}
/**
* Get the test e-mail address.
- *
- * @return string
*/
public static function testEmailAddress(): string
{
- return config('laravel-database-emails.testing.email');
+ return config('database-emails.testing.email');
}
/**
* Get the number of e-mails the cronjob may send at a time.
- *
- * @return int
*/
public static function cronjobEmailLimit(): int
{
- return config('laravel-database-emails.limit', 20);
+ return config('database-emails.limit', 20);
}
/**
* Determine if e-mails should be sent immediately.
- *
- * @return bool
*/
public static function sendImmediately(): bool
{
- return (bool) config('laravel-database-emails.immediately', false);
+ return (bool) config('database-emails.immediately', false);
}
}
diff --git a/src/Email.php b/src/Email.php
index f24831f..5053ca7 100644
--- a/src/Email.php
+++ b/src/Email.php
@@ -4,573 +4,110 @@
namespace Stackkit\LaravelDatabaseEmails;
-use Closure;
-use Exception;
use Carbon\Carbon;
+use Closure;
use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Eloquent\MassPrunable;
use Illuminate\Database\Eloquent\Model;
-use Illuminate\Database\Eloquent\Prunable;
+use Illuminate\Database\Eloquent\Relations\MorphTo;
+use Throwable;
/**
- * @property $id
- * @property $label
- * @property $recipient
- * @property $from
- * @property $cc
- * @property $bcc
- * @property $reply_to
- * @property $subject
- * @property $view
- * @property $variables
- * @property $body
- * @property $attachments
- * @property $attempts
- * @property $sending
- * @property $failed
- * @property $error
- * @property $encrypted
- * @property $queued_at
- * @property $scheduled_at
- * @property $sent_at
- * @property $delivered_at
+ * @property int $id
+ * @property string $label
+ * @property array $recipient
+ * @property array $from
+ * @property array $cc
+ * @property array $bcc
+ * @property array $reply_to
+ * @property string $subject
+ * @property string $view
+ * @property array $variables
+ * @property string $body
+ * @property array $attachments
+ * @property int $attempts
+ * @property int $sending
+ * @property int $failed
+ * @property int $error
+ * @property ?Carbon $queued_at
+ * @property ?Carbon $scheduled_at
+ * @property ?Carbon $sent_at
+ * @property ?Carbon $delivered_at
*/
class Email extends Model
{
- use HasEncryptedAttributes;
- use Prunable;
+ use MassPrunable;
+
+ protected $casts = [
+ 'failed' => 'boolean',
+ 'recipient' => 'json',
+ 'from' => 'json',
+ 'cc' => 'json',
+ 'bcc' => 'json',
+ 'reply_to' => 'json',
+ 'variables' => 'json',
+ 'attachments' => 'json',
+ ];
- /**
- * The table in which the e-mails are stored.
- *
- * @var string
- */
protected $table = 'emails';
- /**
- * The guarded fields.
- *
- * @var array
- */
protected $guarded = [];
public static ?Closure $pruneQuery = null;
- /**
- * Compose a new e-mail.
- *
- * @return EmailComposer
- */
public static function compose(): EmailComposer
{
return new EmailComposer(new static());
}
- /**
- * Get the e-mail id.
- *
- * @return int
- */
- public function getId(): int
- {
- return $this->id;
- }
-
- /**
- * Get the e-mail label.
- *
- * @return string|null
- */
- public function getLabel(): ?string
- {
- return $this->label;
- }
-
- /**
- * Get the e-mail recipient.
- *
- * @return string|array
- */
- public function getRecipient()
- {
- return $this->recipient;
- }
-
- /**
- * Get the e-mail recipient.
- *
- * @return string|array
- */
- public function getRecipientAttribute()
- {
- return $this->recipient;
- }
-
- /**
- * Get the e-mail from.
- *
- * @return array|null
- */
- public function getFrom(): ?array
- {
- return $this->from;
- }
-
- /**
- * Get the e-mail from.
- *
- * @return array|null
- */
- public function getFromAttribute(): ?array
- {
- return $this->from;
- }
-
- /**
- * Get the e-mail from address.
- *
- * @return string|null
- */
- public function getFromAddress(): ?string
- {
- return $this->from['address'] ?? config('mail.from.address');
- }
-
- /**
- * Get the e-mail from address.
- *
- * @return string|null
- */
- public function getFromName(): ?string
- {
- return $this->from['name'] ?? config('mail.from.name');
- }
-
- /**
- * Get the e-mail recipient(s) as string.
- *
- * @return string
- */
- public function getRecipientsAsString(): string
- {
- $glue = ', ';
-
- return implode($glue, (array) $this->recipient);
- }
-
- /**
- * Get the e-mail CC addresses.
- *
- * @return array|string
- */
- public function getCc()
- {
- return $this->cc;
- }
-
- /**
- * Get the e-mail CC addresses.
- *
- * @return array|string
- */
- public function getCcAttribute()
- {
- return $this->cc;
- }
-
- /**
- * Get the e-mail BCC addresses.
- *
- * @return array|string
- */
- public function getBcc()
- {
- return $this->bcc;
- }
-
- /**
- * Get the e-mail BCC addresses.
- *
- * @return array|string
- */
- public function getBccAttribute()
- {
- return $this->bcc;
- }
-
- /**
- * Get the e-mail reply-to addresses.
- *
- * @return array|string
- */
- public function getReplyTo()
- {
- return $this->reply_to;
- }
-
- /**
- * Get the e-mail reply-to addresses.
- *
- * @return array|string
- */
- public function getReplyToAttribute()
- {
- return $this->reply_to;
- }
-
- /**
- * Get the e-mail subject.
- *
- * @return string
- */
- public function getSubject(): string
- {
- return $this->subject;
- }
-
- /**
- * Get the e-mail subject.
- *
- * @return string
- */
- public function getSubjectAttribute(): string
- {
- return $this->subject;
- }
-
- /**
- * Get the e-mail view.
- *
- * @return string
- */
- public function getView(): string
- {
- return $this->view;
- }
-
- /**
- * Get the e-mail variables.
- *
- * @return array|null
- */
- public function getVariables(): ?array
- {
- return $this->variables;
- }
-
- /**
- * Get the e-mail variables.
- *
- * @return array|null
- */
- public function getVariablesAttribute(): ?array
- {
- return $this->variables;
- }
-
- /**
- * Get the e-mail body.
- *
- * @return string
- */
- public function getBody(): string
- {
- return $this->body;
- }
-
- /**
- * Get the e-mail body.
- *
- * @return string
- */
- public function getBodyAttribute(): string
- {
- return $this->body;
- }
-
- /**
- * Get the e-mail attachments.
- *
- * @return array
- */
- public function getAttachments(): array
- {
- return $this->attachments;
- }
-
- /**
- * Get the number of times this e-mail was attempted to send.
- *
- * @return int
- */
- public function getAttempts(): int
- {
- return $this->attempts;
- }
-
- /**
- * Get the queued date.
- *
- * @return string|null
- */
- public function getQueuedDate(): ?string
- {
- return $this->queued_at;
- }
-
- /**
- * Get the queued date as a Carbon instance.
- *
- * @return Carbon
- */
- public function getQueuedDateAsCarbon(): Carbon
- {
- if ($this->queued_at instanceof Carbon) {
- return $this->queued_at;
- }
-
- return Carbon::parse($this->queued_at);
- }
-
- /**
- * Get the scheduled date.
- *
- * @return string|null
- */
- public function getScheduledDate(): ?string
- {
- return $this->scheduled_at;
- }
-
- /**
- * Determine if the e-mail has variables defined.
- *
- * @return bool
- */
- public function hasVariables(): bool
- {
- return ! is_null($this->variables);
- }
-
- /**
- * Get the scheduled date as a Carbon instance.
- *
- * @return Carbon
- */
- public function getScheduledDateAsCarbon(): Carbon
- {
- if ($this->scheduled_at instanceof Carbon) {
- return $this->scheduled_at;
- }
-
- return Carbon::parse($this->scheduled_at);
- }
-
- /**
- * Get the send date for this e-mail.
- *
- * @return string|null
- */
- public function getSendDate(): ?string
- {
- return $this->sent_at;
- }
-
- /**
- * Get the send error.
- *
- * @return string|string
- */
- public function getError(): ?string
- {
- return $this->error;
- }
-
- /**
- * Determine if the e-mail should be sent with custom from values.
- *
- * @return bool
- */
- public function hasFrom(): bool
- {
- return is_array($this->from) && count($this->from) > 0;
- }
-
- /**
- * Determine if the e-mail should be sent as a carbon copy.
- *
- * @return bool
- */
- public function hasCc(): bool
- {
- return strlen($this->getRawDatabaseValue('cc')) > 0;
- }
-
- /**
- * Determine if the e-mail should be sent as a blind carbon copy.
- *
- * @return bool
- */
- public function hasBcc(): bool
- {
- return strlen($this->getRawDatabaseValue('bcc')) > 0;
- }
-
- /**
- * Determine if the e-mail should sent with reply-to.
- *
- * @return bool
- */
- public function hasReplyTo(): bool
- {
- return strlen($this->getRawDatabaseValue('reply_to') ?: '') > 0;
- }
-
- /**
- * Determine if the e-mail is scheduled to be sent later.
- *
- * @return bool
- */
- public function isScheduled(): bool
- {
- return ! is_null($this->getScheduledDate());
- }
-
- /**
- * Determine if the e-mail is encrypted.
- *
- * @return bool
- */
- public function isEncrypted(): bool
- {
- return (bool) $this->getRawDatabaseValue('encrypted');
- }
-
- /**
- * Determine if the e-mail is sent.
- *
- * @return bool
- */
public function isSent(): bool
{
return ! is_null($this->sent_at);
}
- /**
- * Determine if the e-mail failed to be sent.
- *
- * @return bool
- */
public function hasFailed(): bool
{
return $this->failed == 1;
}
- /**
- * Mark the e-mail as sending.
- *
- * @return void
- */
public function markAsSending(): void
{
$this->update([
'attempts' => $this->attempts + 1,
- 'sending' => 1,
+ 'sending' => 1,
]);
}
- /**
- * Mark the e-mail as sent.
- *
- * @return void
- */
public function markAsSent(): void
{
- $now = Carbon::now()->toDateTimeString();
-
$this->update([
'sending' => 0,
- 'sent_at' => $now,
- 'failed' => 0,
- 'error' => '',
+ 'sent_at' => now(),
+ 'failed' => 0,
+ 'error' => '',
]);
}
- /**
- * Mark the e-mail as failed.
- *
- * @param Exception $exception
- * @return void
- */
- public function markAsFailed(Exception $exception): void
+ public function markAsFailed(Throwable $exception): void
{
$this->update([
'sending' => 0,
- 'failed' => 1,
- 'error' => (string) $exception,
+ 'failed' => 1,
+ 'error' => (string) $exception,
]);
}
- /**
- * Send the e-mail.
- *
- * @return void
- */
public function send(): void
{
(new Sender())->send($this);
}
- /**
- * Retry sending the e-mail.
- *
- * @return void
- */
- public function retry(): void
- {
- $retry = $this->replicate();
-
- $retry->fill(
- [
- 'id' => null,
- 'attempts' => 0,
- 'sending' => 0,
- 'failed' => 0,
- 'error' => null,
- 'sent_at' => null,
- 'delivered_at' => null,
- ]
- );
-
- $retry->save();
- }
-
- /**
- * @param string $key
- * @param mixed $default
- * @return mixed
- */
- public function getRawDatabaseValue(string $key = null, $default = null)
- {
- if (method_exists($this, 'getRawOriginal')) {
- return $this->getRawOriginal($key, $default);
- }
-
- return $this->getOriginal($key, $default);
- }
-
- /**
- * @param Closure $closure
- * @return void
- */
- public static function pruneWhen(Closure $closure)
+ public static function pruneWhen(Closure $closure): void
{
static::$pruneQuery = $closure;
}
- /**
- * @return Builder
- */
- public function prunable()
+ public function prunable(): Builder
{
if (static::$pruneQuery) {
return (static::$pruneQuery)($this);
@@ -578,4 +115,9 @@ public function prunable()
return $this->where('created_at', '<', now()->subMonths(6));
}
+
+ public function model(): MorphTo
+ {
+ return $this->morphTo();
+ }
}
diff --git a/src/EmailComposer.php b/src/EmailComposer.php
index 352e63e..fc4d67c 100644
--- a/src/EmailComposer.php
+++ b/src/EmailComposer.php
@@ -4,308 +4,180 @@
namespace Stackkit\LaravelDatabaseEmails;
+use Closure;
+use Illuminate\Contracts\Translation\HasLocalePreference;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Foundation\Auth\User;
use Illuminate\Mail\Mailable;
+use Illuminate\Mail\Mailables\Content;
+use Illuminate\Mail\Mailables\Envelope;
+use Illuminate\Support\Carbon;
class EmailComposer
{
- /**
- * The e-mail that is being composed.
- *
- * @var Email
- */
- private $email;
-
- /**
- * The e-email data.
- *
- * @var array
- */
- protected $data = [];
-
- /**
- * Create a new EmailComposer instance.
- *
- * @param Email $email
- */
- public function __construct(Email $email)
- {
- $this->email = $email;
- }
+ public ?Mailable $mailable = null;
+
+ public ?Envelope $envelope = null;
+
+ public ?Content $content = null;
+
+ public ?array $attachments = null;
+
+ public ?string $locale = null;
+
+ public ?Model $model = null;
+
+ public ?bool $queued = null;
- /**
- * Get the e-mail that is being composed.
- *
- * @return Email
- */
- public function getEmail(): Email
+ public ?string $connection = null;
+
+ public ?string $queue = null;
+
+ public int|Carbon|null $delay = null;
+
+ public ?string $jobClass = null;
+
+ public function __construct(public Email $email)
{
- return $this->email;
+ //
}
- /**
- * Set a data value.
- *
- * @param string $key
- * @param mixed $value
- * @return self
- */
- public function setData(string $key, $value): self
+ public function envelope(null|Envelope|Closure $envelope = null): self
{
- $this->data[$key] = $value;
+ if ($envelope instanceof Closure) {
+ $this->envelope = $envelope($this->envelope ?: new Envelope());
+
+ return $this;
+ }
+
+ $this->envelope = $envelope;
return $this;
}
- /**
- * Get a data value.
- *
- * @param string $key
- * @param mixed $default
- * @return mixed
- */
- public function getData(string $key, $default = null)
+ public function content(null|Content|Closure $content = null): self
{
- if (! is_null($default) && ! $this->hasData($key)) {
- return $default;
+ if ($content instanceof Closure) {
+ $this->content = $content($this->content ?: new Content());
+
+ return $this;
}
- return $this->data[$key];
- }
+ $this->content = $content;
- /**
- * Determine if the given data value was set.
- *
- * @param string $key
- * @return bool
- */
- public function hasData(string $key): bool
- {
- return isset($this->data[$key]);
+ return $this;
}
- /**
- * Set the e-mail label.
- *
- * @param string $label
- * @return self
- */
- public function label(string $label): self
+ public function attachments(null|array|Closure $attachments = null): self
{
- return $this->setData('label', $label);
- }
+ if ($attachments instanceof Closure) {
+ $this->attachments = $attachments($this->attachments ?: []);
- /**
- * Set the e-mail from address and aname.
- *
- * @param string|null $address
- * @param string|null $name
- * @return self
- */
- public function from(?string $address = null, ?string $name = null): self
- {
- return $this->setData('from', compact('address', 'name'));
- }
+ return $this;
+ }
- /**
- * Set the e-mail recipient(s).
- *
- * @param string|array $recipient
- * @return self
- */
- public function recipient($recipient): self
- {
- return $this->setData('recipient', $recipient);
- }
+ $this->attachments = $attachments;
- /**
- * Define the carbon-copy address(es).
- *
- * @param string|array $cc
- * @return self
- */
- public function cc($cc): self
- {
- return $this->setData('cc', $cc);
+ return $this;
}
- /**
- * Define the blind carbon-copy address(es).
- *
- * @param string|array $bcc
- * @return self
- */
- public function bcc($bcc): self
+ public function user(User $user)
{
- return $this->setData('bcc', $bcc);
- }
+ return $this->envelope(function (Envelope $envelope) use ($user) {
+ $name = method_exists($user, 'preferredEmailName')
+ ? $user->preferredEmailName()
+ : ($user->name ?? null);
- /**
- * Define the reply-to address(es).
- *
- * @param string|array $replyTo
- * @return self
- */
- public function replyTo($replyTo): self
- {
- return $this->setData('reply_to', $replyTo);
- }
+ $email = method_exists($user, 'preferredEmailAddress')
+ ? $user->preferredEmailAddress()
+ : $user->email;
- /**
- * Set the e-mail subject.
- *
- * @param string $subject
- * @return self
- */
- public function subject(string $subject): self
- {
- return $this->setData('subject', $subject);
- }
+ if ($user instanceof HasLocalePreference) {
+ $this->locale = $user->preferredLocale();
+ }
- /**
- * Set the e-mail view.
- *
- * @param string $view
- * @return self
- */
- public function view(string $view): self
- {
- return $this->setData('view', $view);
+ return $envelope->to($email, $name);
+ });
}
- /**
- * Set the e-mail variables.
- *
- * @param array $variables
- * @return self
- */
- public function variables(array $variables): self
+ public function model(Model $model)
{
- return $this->setData('variables', $variables);
+ $this->model = $model;
+
+ return $this;
}
- /**
- * Schedule the e-mail.
- *
- * @param mixed $scheduledAt
- * @return Email
- */
- public function schedule($scheduledAt): Email
+ public function label(string $label): self
{
- return $this->later($scheduledAt);
+ $this->email->label = $label;
+
+ return $this;
}
- /**
- * Schedule the e-mail.
- *
- * @param mixed $scheduledAt
- * @return Email
- */
public function later($scheduledAt): Email
{
- $this->setData('scheduled_at', $scheduledAt);
+ $this->email->scheduled_at = Carbon::parse($scheduledAt);
return $this->send();
}
- /**
- * Queue the e-mail.
- *
- * @param string|null $connection
- * @param string|null $queue
- * @param \DateTimeInterface|\DateInterval|int|null $delay
- * @return Email
- */
- public function queue(?string $connection = null, ?string $queue = null, $delay = null): Email
+ public function queue(?string $connection = null, ?string $queue = null, $delay = null, ?string $jobClass = null): Email
{
$connection = $connection ?: config('queue.default');
$queue = $queue ?: 'default';
- $this->setData('queued', true);
- $this->setData('connection', $connection);
- $this->setData('queue', $queue);
- $this->setData('delay', $delay);
+ $this->email->queued_at = now();
+
+ $this->queued = true;
+ $this->connection = $connection;
+ $this->queue = $queue;
+ $this->delay = $delay;
+ $this->jobClass = $jobClass;
return $this->send();
}
- /**
- * Set the Mailable.
- *
- * @param Mailable $mailable
- * @return self
- */
public function mailable(Mailable $mailable): self
{
- $this->setData('mailable', $mailable);
+ $this->mailable = $mailable;
(new MailableReader())->read($this);
return $this;
}
- /**
- * Attach a file to the e-mail.
- *
- * @param string $file
- * @param array $options
- * @return self
- */
- public function attach(string $file, array $options = []): self
- {
- $attachments = $this->hasData('attachments') ? $this->getData('attachments') : [];
-
- $attachments[] = compact('file', 'options');
-
- return $this->setData('attachments', $attachments);
- }
-
- /**
- * Attach in-memory data as an attachment.
- *
- * @param string $data
- * @param string $name
- * @param array $options
- * @return self
- */
- public function attachData(string $data, string $name, array $options = []): self
- {
- $attachments = $this->hasData('rawAttachments') ? $this->getData('rawAttachments') : [];
-
- $attachments[] = compact('data', 'name', 'options');
-
- return $this->setData('rawAttachments', $attachments);
- }
-
- /**
- * Send the e-mail.
- *
- * @return Email
- */
public function send(): Email
{
- (new Validator())->validate($this);
-
- (new Preparer())->prepare($this);
+ if ($this->envelope && $this->content) {
+ (new MailableReader())->read($this);
+ }
- if (Config::encryptEmails()) {
- (new Encrypter())->encrypt($this);
+ if (! $this->email->from) {
+ $this->email->from = [
+ 'address' => config('mail.from.address'),
+ 'name' => config('mail.from.name'),
+ ];
}
$this->email->save();
$this->email->refresh();
- if ($this->getData('queued', false) === true) {
- dispatch(new SendEmailJob($this->email))
- ->onConnection($this->getData('connection'))
- ->onQueue($this->getData('queue'))
- ->delay($this->getData('delay'));
+ if (Config::sendImmediately()) {
+ $this->email->send();
return $this->email;
}
- if (Config::sendImmediately()) {
- $this->email->send();
+ if ($this->queued) {
+ $job = $this->jobClass ?: SendEmailJob::class;
+
+ dispatch(new $job($this->email))
+ ->onConnection($this->connection)
+ ->onQueue($this->queue)
+ ->delay($this->delay);
+
+ return $this->email;
}
return $this->email;
diff --git a/src/Encrypter.php b/src/Encrypter.php
deleted file mode 100644
index 14052a7..0000000
--- a/src/Encrypter.php
+++ /dev/null
@@ -1,130 +0,0 @@
-setEncrypted($composer);
-
- $this->encryptRecipients($composer);
-
- $this->encryptReplyTo($composer);
-
- $this->encryptFrom($composer);
-
- $this->encryptSubject($composer);
-
- $this->encryptVariables($composer);
-
- $this->encryptBody($composer);
- }
-
- /**
- * Mark the e-mail as encrypted.
- *
- * @param EmailComposer $composer
- */
- private function setEncrypted(EmailComposer $composer): void
- {
- $composer->getEmail()->setAttribute('encrypted', 1);
- }
-
- /**
- * Encrypt the e-mail reply-to.
- *
- * @param EmailComposer $composer
- */
- private function encryptReplyTo(EmailComposer $composer): void
- {
- $email = $composer->getEmail();
-
- $email->fill([
- 'reply_to' => $composer->hasData('reply_to') ? encrypt($email->reply_to) : '',
- ]);
- }
-
- /**
- * Encrypt the e-mail addresses of the recipients.
- *
- * @param EmailComposer $composer
- */
- private function encryptRecipients(EmailComposer $composer): void
- {
- $email = $composer->getEmail();
-
- $email->fill([
- 'recipient' => encrypt($email->recipient),
- 'cc' => $composer->hasData('cc') ? encrypt($email->cc) : '',
- 'bcc' => $composer->hasData('bcc') ? encrypt($email->bcc) : '',
- ]);
- }
-
- /**
- * Encrypt the e-mail addresses for the from field.
- *
- * @param EmailComposer $composer
- */
- private function encryptFrom(EmailComposer $composer): void
- {
- $email = $composer->getEmail();
-
- $email->fill([
- 'from' => encrypt($email->from),
- ]);
- }
-
- /**
- * Encrypt the e-mail subject.
- *
- * @param EmailComposer $composer
- */
- private function encryptSubject(EmailComposer $composer): void
- {
- $email = $composer->getEmail();
-
- $email->fill([
- 'subject' => encrypt($email->subject),
- ]);
- }
-
- /**
- * Encrypt the e-mail variables.
- *
- * @param EmailComposer $composer
- */
- private function encryptVariables(EmailComposer $composer): void
- {
- if (! $composer->hasData('variables')) {
- return;
- }
-
- $email = $composer->getEmail();
-
- $email->fill([
- 'variables' => encrypt($email->variables),
- ]);
- }
-
- /**
- * Encrypt the e-mail body.
- *
- * @param EmailComposer $composer
- */
- private function encryptBody(EmailComposer $composer): void
- {
- $email = $composer->getEmail();
-
- $email->fill([
- 'body' => encrypt($email->body),
- ]);
- }
-}
diff --git a/src/HasEncryptedAttributes.php b/src/HasEncryptedAttributes.php
deleted file mode 100644
index 01dfbb4..0000000
--- a/src/HasEncryptedAttributes.php
+++ /dev/null
@@ -1,88 +0,0 @@
-attributes[$key];
-
- if ($this->isEncrypted() && in_array($key, $this->encrypted)) {
- try {
- $value = decrypt($value);
- } catch (DecryptException $e) {
- $value = '';
- }
- }
-
- if (in_array($key, $this->encoded) && is_string($value)) {
- $decoded = json_decode($value, true);
-
- if (! is_null($decoded)) {
- $value = $decoded;
- }
- }
-
- // BC fix for attachments in 4.1.0 and lower.
- // Attachments were stored json encoded.
- // Because this doesn't work for raw attachments, value is now serialized.
- // Check if value is json encoded or serialized, and decode or unserialize accordingly.
- if ($key == 'attachments') {
- if (substr($value, 0, 2) === 'a:') {
- $unserialized = @unserialize($value);
- if ($value !== false) {
- $value = $unserialized;
- }
- } else {
- $decoded = json_decode($value, true);
-
- if (! is_null($decoded)) {
- $value = $decoded;
- }
- }
- }
-
- return $value;
- }
-}
diff --git a/src/LaravelDatabaseEmailsServiceProvider.php b/src/LaravelDatabaseEmailsServiceProvider.php
index 57e249b..30a9ea4 100644
--- a/src/LaravelDatabaseEmailsServiceProvider.php
+++ b/src/LaravelDatabaseEmailsServiceProvider.php
@@ -10,8 +10,6 @@ class LaravelDatabaseEmailsServiceProvider extends ServiceProvider
{
/**
* Perform post-registration booting of services.
- *
- * @return void
*/
public function boot(): void
{
@@ -21,42 +19,32 @@ public function boot(): void
/**
* Boot the config for the package.
- *
- * @return void
*/
private function bootConfig(): void
{
- $baseDir = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR;
- $configDir = $baseDir . 'config' . DIRECTORY_SEPARATOR;
+ $baseDir = __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR;
+ $configDir = $baseDir.'config'.DIRECTORY_SEPARATOR;
$this->publishes([
- $configDir . 'laravel-database-emails.php' => config_path('laravel-database-emails.php'),
- ], 'laravel-database-emails-config');
+ $configDir.'laravel-database-emails.php' => config_path('laravel-database-emails.php'),
+ ], 'database-emails-config');
}
/**
* Boot the database for the package.
- *
- * @return void
*/
private function bootDatabase(): void
{
- $baseDir = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR;
- $migrationsDir = $baseDir . 'database' . DIRECTORY_SEPARATOR . 'migrations' . DIRECTORY_SEPARATOR;
+ $baseDir = __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR;
+ $migrationsDir = $baseDir.'database'.DIRECTORY_SEPARATOR.'migrations'.DIRECTORY_SEPARATOR;
- if ($this->app['config']->get('laravel-database-emails.manual_migrations')) {
- $this->publishes([
- $migrationsDir => "{$this->app->databasePath()}/migrations",
- ], 'laravel-database-emails-migrations');
- } else {
- $this->loadMigrationsFrom([$migrationsDir]);
- }
+ $this->publishes([
+ $migrationsDir => "{$this->app->databasePath()}/migrations",
+ ], 'database-emails-migrations');
}
/**
* Register the service provider.
- *
- * @return void
*/
public function register(): void
{
diff --git a/src/MailableReader.php b/src/MailableReader.php
index 86be654..bdc3876 100644
--- a/src/MailableReader.php
+++ b/src/MailableReader.php
@@ -4,28 +4,58 @@
namespace Stackkit\LaravelDatabaseEmails;
+use Error;
use Exception;
-use Illuminate\Container\Container;
-use ReflectionObject;
+use Illuminate\Mail\Mailable;
+use Illuminate\Mail\Mailables\Content;
+use Illuminate\Mail\Mailables\Envelope;
+use Illuminate\Support\Traits\Localizable;
class MailableReader
{
+ use Localizable;
+
/**
* Read the mailable and pass the data to the email composer.
- *
- * @param EmailComposer $composer
*/
public function read(EmailComposer $composer): void
{
- if (method_exists($composer->getData('mailable'), 'prepareMailableForDelivery')) {
- $reflected = (new ReflectionObject($composer->getData('mailable')));
- $method = $reflected->getMethod('prepareMailableForDelivery');
- $method->setAccessible(true);
- $method->invoke($composer->getData('mailable'));
- } else {
- Container::getInstance()->call([$composer->getData('mailable'), 'build']);
+ $canBeSent = $composer->mailable || ($composer->envelope && $composer->content);
+
+ if (! $canBeSent) {
+ throw new Error('E-mail cannot be sent: no mailable or envelope and content provided.');
+ }
+
+ if ($composer->envelope && $composer->content) {
+ $composer->mailable = new class($composer) extends Mailable
+ {
+ public function __construct(private EmailComposer $composer)
+ {
+ //
+ }
+
+ public function content(): Content
+ {
+ return $this->composer->content;
+ }
+
+ public function envelope(): Envelope
+ {
+ return $this->composer->envelope;
+ }
+
+ public function attachments(): array
+ {
+ return $this->composer->attachments ?? [];
+ }
+ };
}
+ (fn (Mailable $mailable) => $mailable->prepareMailableForDelivery())->call(
+ $composer->mailable,
+ $composer->mailable,
+ );
+
$this->readRecipient($composer);
$this->readFrom($composer);
@@ -41,136 +71,130 @@ public function read(EmailComposer $composer): void
$this->readBody($composer);
$this->readAttachments($composer);
+
+ $this->readModel($composer);
}
/**
* Convert the mailable addresses array into a array with only e-mails.
*
- * @param string $from
- * @return array
+ * @param string $from
*/
private function convertMailableAddresses($from): array
{
- return collect($from)->map(function ($recipient) {
- return $recipient['address'];
+ return collect($from)->mapWithKeys(function ($recipient) {
+ return [$recipient['address'] => $recipient['name']];
})->toArray();
}
/**
* Read the mailable recipient to the email composer.
- *
- * @param EmailComposer $composer
*/
private function readRecipient(EmailComposer $composer): void
{
- $to = $this->convertMailableAddresses(
- $composer->getData('mailable')->to
- );
+ if (config('database-emails.testing.enabled')) {
+ $composer->email->recipient = [
+ config('database-emails.testing.email') => null,
+ ];
- $composer->recipient($to);
+ return;
+ }
+
+ $composer->email->recipient = $this->prepareAddressForDatabaseStorage(
+ $composer->mailable->to);
}
/**
* Read the mailable from field to the email composer.
- *
- * @param EmailComposer $composer
*/
private function readFrom(EmailComposer $composer): void
{
- $from = reset($composer->getData('mailable')->from);
-
- if (!$from) {
- return;
- }
-
- $composer->from(
- $from['address'],
- $from['name']
- );
+ $composer->email->from = head($composer->mailable->from);
}
/**
* Read the mailable cc to the email composer.
- *
- * @param EmailComposer $composer
*/
private function readCc(EmailComposer $composer): void
{
- $cc = $this->convertMailableAddresses(
- $composer->getData('mailable')->cc
- );
-
- $composer->cc($cc);
+ $composer->email->cc = $this->prepareAddressForDatabaseStorage(
+ $composer->mailable->cc);
}
/**
* Read the mailable bcc to the email composer.
- *
- * @param EmailComposer $composer
*/
private function readBcc(EmailComposer $composer): void
{
- $bcc = $this->convertMailableAddresses(
- $composer->getData('mailable')->bcc
- );
-
- $composer->bcc($bcc);
+ $composer->email->bcc = $this->prepareAddressForDatabaseStorage(
+ $composer->mailable->bcc);
}
/**
* Read the mailable reply-to to the email composer.
- *
- * @param EmailComposer $composer
*/
private function readReplyTo(EmailComposer $composer): void
{
- $replyTo = $this->convertMailableAddresses(
- $composer->getData('mailable')->replyTo
- );
-
- $composer->replyTo($replyTo);
+ $composer->email->reply_to = $this->prepareAddressForDatabaseStorage(
+ $composer->mailable->replyTo);
}
/**
* Read the mailable subject to the email composer.
- *
- * @param EmailComposer $composer
*/
private function readSubject(EmailComposer $composer): void
{
- $composer->subject($composer->getData('mailable')->subject);
+ $composer->email->subject = $composer->mailable->subject;
}
/**
* Read the mailable body to the email composer.
*
- * @param EmailComposer $composer
* @throws Exception
*/
private function readBody(EmailComposer $composer): void
{
- $composer->setData('view', '');
+ /** @var Mailable $mailable */
+ $mailable = $composer->mailable;
+
+ $composer->email->view = $mailable->view;
+ $composer->email->variables = $mailable->buildViewData();
- $mailable = $composer->getData('mailable');
+ $localeToUse = $composer->locale ?? app()->currentLocale();
- $composer->setData('body', view($mailable->view, $mailable->buildViewData())->render());
+ $this->withLocale(
+ $localeToUse,
+ fn () => $composer->email->body = view($mailable->view, $mailable->buildViewData())->render(),
+ );
}
/**
* Read the mailable attachments to the email composer.
- *
- * @param EmailComposer $composer
*/
private function readAttachments(EmailComposer $composer): void
{
- $mailable = $composer->getData('mailable');
+ $mailable = $composer->mailable;
- foreach ((array) $mailable->attachments as $attachment) {
- call_user_func_array([$composer, 'attach'], $attachment);
- }
+ $composer->email->attachments = array_map(function (array $attachment) {
+ if (! $attachment['file'] instanceof Attachment) {
+ throw new Error('The attachment is not an instance of '.Attachment::class.'.');
+ }
+
+ return $attachment['file']->toArray();
+ }, $mailable->attachments);
+ }
- foreach ((array) $mailable->rawAttachments as $rawAttachment) {
- call_user_func_array([$composer, 'attachData'], $rawAttachment);
+ public function readModel(EmailComposer $composer): void
+ {
+ if ($composer->model) {
+ $composer->email->model()->associate($composer->model);
}
}
+
+ private function prepareAddressForDatabaseStorage(array $addresses): array
+ {
+ return collect($addresses)->mapWithKeys(function ($recipient) {
+ return [$recipient['address'] => $recipient['name']];
+ })->toArray();
+ }
}
diff --git a/src/Preparer.php b/src/Preparer.php
deleted file mode 100644
index ea405d0..0000000
--- a/src/Preparer.php
+++ /dev/null
@@ -1,287 +0,0 @@
-prepareLabel($composer);
-
- $this->prepareRecipient($composer);
-
- $this->prepareFrom($composer);
-
- $this->prepareCc($composer);
-
- $this->prepareBcc($composer);
-
- $this->prepareReplyTo($composer);
-
- $this->prepareSubject($composer);
-
- $this->prepareView($composer);
-
- $this->prepareVariables($composer);
-
- $this->prepareBody($composer);
-
- $this->prepareAttachments($composer);
-
- $this->prepareScheduled($composer);
-
- $this->prepareImmediately($composer);
-
- $this->prepareQueued($composer);
- }
-
- /**
- * Prepare the label for database storage.
- *
- * @param EmailComposer $composer
- */
- private function prepareLabel(EmailComposer $composer): void
- {
- if (! $composer->hasData('label')) {
- return;
- }
-
- $composer->getEmail()->fill([
- 'label' => $composer->getData('label'),
- ]);
- }
-
- /**
- * Prepare the recipient for database storage.
- *
- * @param EmailComposer $composer
- */
- private function prepareRecipient(EmailComposer $composer): void
- {
- if (Config::testing()) {
- $composer->recipient(Config::testEmailAddress());
- }
-
- $composer->getEmail()->fill([
- 'recipient' => json_encode($composer->getData('recipient')),
- ]);
- }
-
- /**
- * Prepare the from values for database storage.
- *
- * @param EmailComposer $composer
- */
- private function prepareFrom(EmailComposer $composer): void
- {
- $composer->getEmail()->fill([
- 'from' => json_encode($composer->getData('from', '')),
- ]);
- }
-
- /**
- * Prepare the carbon copies for database storage.
- *
- * @param EmailComposer $composer
- */
- private function prepareCc(EmailComposer $composer): void
- {
- if (Config::testing()) {
- $composer->setData('cc', []);
- }
-
- $composer->getEmail()->fill([
- 'cc' => json_encode($composer->getData('cc', [])),
- ]);
- }
-
- /**
- * Prepare the carbon copies for database storage.
- *
- * @param EmailComposer $composer
- */
- private function prepareBcc(EmailComposer $composer): void
- {
- if (Config::testing()) {
- $composer->setData('bcc', []);
- }
-
- $composer->getEmail()->fill([
- 'bcc' => json_encode($composer->getData('bcc', [])),
- ]);
- }
-
- /**
- * Prepare the reply-to for database storage.
- *
- * @param EmailComposer $composer
- */
- private function prepareReplyTo(EmailComposer $composer): void
- {
- $value = $composer->getData('reply_to', []);
-
- if (! is_array($value)) {
- $value = [$value];
- }
-
- foreach ($value as $i => $v) {
- if ($v instanceof Address) {
- $value[$i] = [
- 'address' => $v->address,
- 'name' => $v->name,
- ];
- }
- }
-
- $composer->getEmail()->fill([
- 'reply_to' => json_encode($value),
- ]);
- }
-
- /**
- * Prepare the subject for database storage.
- *
- * @param EmailComposer $composer
- */
- private function prepareSubject(EmailComposer $composer): void
- {
- $composer->getEmail()->fill([
- 'subject' => $composer->getData('subject'),
- ]);
- }
-
- /**
- * Prepare the view for database storage.
- *
- * @param EmailComposer $composer
- */
- private function prepareView(EmailComposer $composer): void
- {
- $composer->getEmail()->fill([
- 'view' => $composer->getData('view'),
- ]);
- }
-
- /**
- * Prepare the variables for database storage.
- *
- * @param EmailComposer $composer
- */
- private function prepareVariables(EmailComposer $composer): void
- {
- if (! $composer->hasData('variables')) {
- return;
- }
-
- $composer->getEmail()->fill([
- 'variables' => json_encode($composer->getData('variables')),
- ]);
- }
-
- /**
- * Prepare the e-mail body for database storage.
- *
- * @param EmailComposer $composer
- */
- private function prepareBody(EmailComposer $composer): void
- {
- // If the body was predefined (by for example a mailable), use that.
- if ($composer->hasData('body')) {
- $body = $composer->getData('body');
- } else {
- $body = view(
- $composer->getData('view'),
- $composer->hasData('variables') ? $composer->getData('variables') : []
- )->render();
- }
-
- $composer->getEmail()->fill(compact('body'));
- }
-
- /**
- * Prepare the e-mail attachments.
- *
- * @param EmailComposer $composer
- */
- private function prepareAttachments(EmailComposer $composer): void
- {
- $attachments = [];
-
- foreach ((array) $composer->getData('attachments', []) as $attachment) {
- $attachments[] = [
- 'type' => 'attachment',
- 'attachment' => $attachment,
- ];
- }
-
- foreach ((array) $composer->getData('rawAttachments', []) as $rawAttachment) {
- $attachments[] = [
- 'type' => 'rawAttachment',
- 'attachment' => $rawAttachment,
- ];
- }
-
- $composer->getEmail()->fill([
- 'attachments' => serialize($attachments),
- ]);
- }
-
- /**
- * Prepare the scheduled date for database storage.
- *
- * @param EmailComposer $composer
- */
- private function prepareScheduled(EmailComposer $composer): void
- {
- if (! $composer->hasData('scheduled_at')) {
- return;
- }
-
- $scheduled = $composer->getData('scheduled_at');
-
- if (is_string($scheduled)) {
- $scheduled = Carbon::parse($scheduled);
- }
-
- $composer->getEmail()->fill([
- 'scheduled_at' => $scheduled->toDateTimeString(),
- ]);
- }
-
- /**
- * Prepare the e-mail so it can be sent immediately.
- *
- * @param EmailComposer $composer
- */
- private function prepareImmediately(EmailComposer $composer): void
- {
- if (Config::sendImmediately()) {
- $composer->getEmail()->fill(['sending' => 1]);
- }
- }
-
- /**
- * Prepare the queued date.
- *
- * @param EmailComposer $composer
- */
- private function prepareQueued(EmailComposer $composer): void
- {
- if ($composer->getData('queued', false) === true) {
- $composer->getEmail()->fill([
- 'queued_at' => Carbon::now()->toDateTimeString(),
- ]);
- }
- }
-
-}
diff --git a/src/SendEmailJob.php b/src/SendEmailJob.php
index a7ba528..a865048 100644
--- a/src/SendEmailJob.php
+++ b/src/SendEmailJob.php
@@ -26,6 +26,6 @@ public function __construct(Email $email)
public function handle(): void
{
- (new Sender())->send($this->email);
+ $this->email->send();
}
}
diff --git a/src/SendEmailsCommand.php b/src/SendEmailsCommand.php
index 039ca37..71817ee 100644
--- a/src/SendEmailsCommand.php
+++ b/src/SendEmailsCommand.php
@@ -4,96 +4,42 @@
namespace Stackkit\LaravelDatabaseEmails;
-use Exception;
use Illuminate\Console\Command;
-use Illuminate\Database\Eloquent\Collection;
+use Throwable;
class SendEmailsCommand extends Command
{
- /**
- * The name and signature of the console command.
- *
- * @var string
- */
protected $signature = 'email:send';
- /**
- * The console command description.
- *
- * @var string
- */
protected $description = 'Send all queued e-mails';
- /**
- * The e-mail repository.
- *
- * @var Store
- */
- protected $store;
-
- /**
- * Create a new SendEmailsCommand instance.
- *
- * @param Store $store
- */
- public function __construct(Store $store)
+ public function handle(Store $store): void
{
- parent::__construct();
-
- $this->store = $store;
- }
-
- /**
- * Execute the console command.
- *
- * @return void
- */
- public function handle(): void
- {
- $emails = $this->store->getQueue();
+ $emails = $store->getQueue();
if ($emails->isEmpty()) {
- $this->line('There is nothing to send.');
+ $this->components->info('There is nothing to send.');
return;
}
- $progress = $this->output->createProgressBar($emails->count());
+ $this->components->info('Sending '.count($emails).' e-mail(s).');
foreach ($emails as $email) {
- $progress->advance();
+ $recipients = implode(', ', array_keys($email->recipient));
+ $line = str($email->subject)->limit(40).' - '.str($recipients)->limit(40);
- try {
+ rescue(function () use ($email, $line) {
$email->send();
- } catch (Exception $e) {
- $email->markAsFailed($e);
- }
- }
- $progress->finish();
-
- $this->result($emails);
- }
-
- /**
- * Output a table with the cronjob result.
- *
- * @param Collection $emails
- * @return void
- */
- protected function result(Collection $emails): void
- {
- $headers = ['ID', 'Recipient', 'Subject', 'Status'];
+ $this->components->twoColumnDetail($line, 'DONE>');
+ }, function (Throwable $e) use ($email, $line) {
+ $email->markAsFailed($e);
- $this->line("\n");
+ $this->components->twoColumnDetail($line, 'FAIL>');
+ });
+ }
- $this->table($headers, $emails->map(function (Email $email) {
- return [
- $email->getId(),
- $email->getRecipientsAsString(),
- $email->getSubject(),
- $email->hasFailed() ? 'Failed' : 'OK',
- ];
- }));
+ $this->newLine();
}
}
diff --git a/src/Sender.php b/src/Sender.php
index 60a80fe..736c830 100644
--- a/src/Sender.php
+++ b/src/Sender.php
@@ -4,16 +4,12 @@
namespace Stackkit\LaravelDatabaseEmails;
+use Illuminate\Mail\Attachment;
use Illuminate\Mail\Message;
use Illuminate\Support\Facades\Mail;
class Sender
{
- /**
- * Send the given e-mail.
- *
- * @param Email $email
- */
public function send(Email $email): void
{
if ($email->isSent()) {
@@ -26,45 +22,39 @@ public function send(Email $email): void
$this->buildMessage($message, $email);
});
- // This is used so we can assert things on the sent message in Laravel 9+ since we cannot use
- // the Swift Mailer plugin anymore. So this is purely used for in the PHPUnit tests.
- if (version_compare(app()->version(), '9.0.0', '>=') && !is_null($sentMessage)) {
- event(new MessageSent($sentMessage));
- }
+ event(new MessageSent($sentMessage));
$email->markAsSent();
}
- /**
- * Build the e-mail message.
- *
- * @param Message $message
- * @param Email $email
- */
private function buildMessage(Message $message, Email $email): void
{
- $message->to($email->getRecipient())
- ->cc($email->hasCc() ? $email->getCc() : [])
- ->bcc($email->hasBcc() ? $email->getBcc() : [])
- ->replyTo($email->hasReplyTo() ? $email->getReplyTo() : [])
- ->subject($email->getSubject())
- ->from($email->getFromAddress(), $email->getFromName());
-
- if (version_compare(app()->version(), '9.0.0', '>=')) {
- // Symfony Mailer
- $message->html($email->getBody());
- } else {
- // SwiftMail
- $message->setBody($email->getBody(), 'text/html');
- }
-
- $attachmentMap = [
- 'attachment' => 'attach',
- 'rawAttachment' => 'attachData',
- ];
-
- foreach ($email->getAttachments() as $attachment) {
- call_user_func_array([$message, $attachmentMap[$attachment['type']]], $attachment['attachment']);
+ $message->to($email->recipient)
+ ->cc($email->cc ?: [])
+ ->bcc($email->bcc ?: [])
+ ->replyTo($email->reply_to ?: [])
+ ->subject($email->subject)
+ ->from($email->from['address'], $email->from['name'])
+ ->html($email->body);
+
+ foreach ($email->attachments as $dbAttachment) {
+ $attachment = match (true) {
+ isset($dbAttachment['disk']) => Attachment::fromStorageDisk(
+ $dbAttachment['disk'],
+ $dbAttachment['path']
+ ),
+ default => Attachment::fromPath($dbAttachment['path']),
+ };
+
+ if (! empty($dbAttachment['mime'])) {
+ $attachment->withMime($dbAttachment['mime']);
+ }
+
+ if (! empty($dbAttachment['as'])) {
+ $attachment->as($dbAttachment['as']);
+ }
+
+ $message->attach($attachment);
}
}
}
diff --git a/src/SentMessage.php b/src/SentMessage.php
index 570d524..f3dff66 100644
--- a/src/SentMessage.php
+++ b/src/SentMessage.php
@@ -4,22 +4,30 @@
namespace Stackkit\LaravelDatabaseEmails;
-use Swift_Mime_SimpleMimeEntity;
+use Symfony\Component\Mime\Email;
use Symfony\Component\Mime\Part\DataPart;
class SentMessage
{
public $from = [];
+
public $to = [];
+
public $cc = [];
+
public $bcc = [];
+
public $replyTo = [];
+
public $subject = '';
+
public $body = '';
+
public $attachments = [];
+
public $headers = [];
- public static function createFromSymfonyMailer(\Symfony\Component\Mime\Email $email): SentMessage
+ public static function createFromSymfonyMailer(Email $email): SentMessage
{
$sentMessage = new self();
@@ -54,25 +62,4 @@ public static function createFromSymfonyMailer(\Symfony\Component\Mime\Email $em
return $sentMessage;
}
-
- public static function createFromSwiftMailer(\Swift_Mime_SimpleMessage $message): SentMessage
- {
- $sentMessage = new self();
-
- $sentMessage->from = $message->getFrom();
- $sentMessage->to = $message->getTo();
- $sentMessage->cc = $message->getCc();
- $sentMessage->bcc = $message->getBcc();
- $sentMessage->replyTo = $message->getReplyTo();
- $sentMessage->subject = $message->getSubject();
- $sentMessage->body = $message->getBody();
- $sentMessage->attachments = array_map(function(Swift_Mime_SimpleMimeEntity $entity) {
- return [
- 'body' => $entity->getBody(),
- 'disposition' => $entity->getContentType() . ' ' . $entity->getHeaders()->get('content-disposition'),
- ];
- }, $message->getChildren());
-
- return $sentMessage;
- }
}
diff --git a/src/Store.php b/src/Store.php
index 3a9f4f2..bd26544 100644
--- a/src/Store.php
+++ b/src/Store.php
@@ -6,6 +6,7 @@
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Support\LazyCollection;
class Store
{
@@ -14,11 +15,11 @@ class Store
*
* @return Collection|Email[]
*/
- public function getQueue(): Collection
+ public function getQueue(): LazyCollection
{
$query = new Email();
- return $query
+ return Email::query()
->whereNull('deleted_at')
->whereNull('sent_at')
->whereNull('queued_at')
@@ -30,6 +31,6 @@ public function getQueue(): Collection
->where('attempts', '<', Config::maxAttemptCount())
->orderBy('created_at', 'asc')
->limit(Config::cronjobEmailLimit())
- ->get();
+ ->cursor();
}
}
diff --git a/src/Validator.php b/src/Validator.php
deleted file mode 100644
index 69d130f..0000000
--- a/src/Validator.php
+++ /dev/null
@@ -1,224 +0,0 @@
-validateLabel($composer);
-
- $this->validateRecipient($composer);
-
- $this->validateCc($composer);
-
- $this->validateBcc($composer);
-
- $this->validateReplyTo($composer);
-
- $this->validateSubject($composer);
-
- $this->validateView($composer);
-
- $this->validateVariables($composer);
-
- $this->validateScheduled($composer);
- }
-
- /**
- * Validate the defined label.
- *
- * @param EmailComposer $composer
- * @throws InvalidArgumentException
- */
- private function validateLabel(EmailComposer $composer): void
- {
- if ($composer->hasData('label') && strlen($composer->getData('label')) > 255) {
- throw new InvalidArgumentException('The given label [' . $composer->getData('label') . '] is too large for database storage');
- }
- }
-
- /**
- * Validate the given recipient(s).
- *
- * @param EmailComposer $composer
- * @throws InvalidArgumentException
- */
- private function validateRecipient(EmailComposer $composer): void
- {
- if (! $composer->hasData('recipient')) {
- throw new InvalidArgumentException('No recipient specified');
- }
-
- $recipients = (array) $composer->getData('recipient');
-
- if (count($recipients) == 0) {
- throw new InvalidArgumentException('No recipient specified');
- }
-
- foreach ($recipients as $recipient) {
- if (! filter_var($recipient, FILTER_VALIDATE_EMAIL)) {
- throw new InvalidArgumentException('E-mail address [' . $recipient . '] is invalid');
- }
- }
- }
-
- /**
- * Validate the carbon copy e-mail addresses.
- *
- * @param EmailComposer $composer
- * @throws InvalidArgumentException
- */
- private function validateCc(EmailComposer $composer): void
- {
- if (! $composer->hasData('cc')) {
- return;
- }
-
- foreach ((array) $composer->getData('cc') as $cc) {
- if (! filter_var($cc, FILTER_VALIDATE_EMAIL)) {
- throw new InvalidArgumentException('E-mail address [' . $cc . '] is invalid');
- }
- }
- }
-
- /**
- * Validate the blind carbon copy e-mail addresses.
- *
- * @param EmailComposer $composer
- * @throws InvalidArgumentException
- */
- private function validateBcc(EmailComposer $composer): void
- {
- if (! $composer->hasData('bcc')) {
- return;
- }
-
- foreach ((array) $composer->getData('bcc') as $bcc) {
- if (! filter_var($bcc, FILTER_VALIDATE_EMAIL)) {
- throw new InvalidargumentException('E-mail address [' . $bcc . '] is invalid');
- }
- }
- }
-
- /**
- * Validate the reply-to addresses.
- *
- * @param EmailComposer $composer
- * @throws InvalidArgumentException
- */
- private function validateReplyTo(EmailComposer $composer): void
- {
- if (! $composer->hasData('reply_to')) {
- return;
- }
-
- foreach (Arr::wrap($composer->getData('reply_to')) as $replyTo) {
- if ($replyTo instanceof Address) {
- $replyTo = $replyTo->address;
- }
-
- if (! filter_var($replyTo, FILTER_VALIDATE_EMAIL)) {
- throw new InvalidargumentException('E-mail address [' . $replyTo . '] is invalid');
- }
- }
- }
-
- /**
- * Validate the e-mail subject.
- *
- * @param EmailComposer $composer
- * @throws InvalidArgumentException
- */
- private function validateSubject(EmailComposer $composer): void
- {
- if (! $composer->hasData('subject')) {
- throw new InvalidArgumentException('No subject specified');
- }
- }
-
- /**
- * Validate the e-mail view.
- *
- * @param EmailComposer $composer
- * @throws InvalidARgumentException
- */
- private function validateView(EmailComposer $composer): void
- {
- if ($composer->hasData('mailable')) {
- return;
- }
-
- if (! $composer->hasData('view')) {
- throw new InvalidArgumentException('No view specified');
- }
-
- $view = $composer->getData('view');
-
- if (! view()->exists($view)) {
- throw new InvalidArgumentException('View [' . $view . '] does not exist');
- }
- }
-
- /**
- * Validate the e-mail variables.
- *
- * @param EmailComposer $composer
- * @throws InvalidArgumentException
- */
- private function validateVariables(EmailComposer $composer): void
- {
- if ($composer->hasData('variables') && ! is_array($composer->getData('variables'))) {
- throw new InvalidArgumentException('Variables must be an array');
- }
- }
-
- /**
- * Validate the scheduled date.
- *
- * @param EmailComposer $composer
- * @throws InvalidArgumentException
- */
- private function validateScheduled(EmailComposer $composer): void
- {
- if (! $composer->hasData('scheduled_at')) {
- return;
- }
-
- $scheduled = $composer->getData('scheduled_at');
-
- if (! $scheduled instanceof Carbon && ! is_string($scheduled)) {
- throw new InvalidArgumentException('Scheduled date must be a Carbon\Carbon instance or a strtotime-valid string');
- }
-
- if (is_string($scheduled)) {
- try {
- Carbon::parse($scheduled);
- } catch (Exception $e) {
- throw new InvalidArgumentException('Scheduled date could not be parsed by Carbon: ' . $e->getMessage());
- }
- }
- }
-}
diff --git a/testbench.yaml b/testbench.yaml
new file mode 100644
index 0000000..2195c53
--- /dev/null
+++ b/testbench.yaml
@@ -0,0 +1,23 @@
+providers:
+ - Workbench\App\Providers\WorkbenchServiceProvider
+ - Stackkit\LaravelDatabaseEmails\LaravelDatabaseEmailsServiceProvider
+
+migrations:
+ - workbench/database/migrations
+
+seeders:
+ - Workbench\Database\Seeders\DatabaseSeeder
+
+workbench:
+ start: '/'
+ install: true
+ health: false
+ discovers:
+ web: true
+ api: false
+ commands: false
+ components: false
+ views: true
+ build: []
+ assets: []
+ sync: []
diff --git a/tests/ComposeTest.php b/tests/ComposeTest.php
new file mode 100644
index 0000000..49e88b9
--- /dev/null
+++ b/tests/ComposeTest.php
@@ -0,0 +1,53 @@
+ 'John Doe',
+ 'email' => 'johndoe@example.com',
+ 'password' => 'secret',
+ ]);
+
+ $email = Email::compose()
+ ->user($user)
+ ->model($user)
+ ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey'))
+ ->content(fn (Content $content) => $content->view('welcome'))
+ ->send();
+
+ $this->assertEquals($email->model_type, $user->getMorphClass());
+ $this->assertEquals($email->model_id, $user->getKey());
+ }
+
+ #[Test]
+ public function models_can_be_empty(): void
+ {
+ $user = User::forceCreate([
+ 'name' => 'John Doe',
+ 'email' => 'johndoe@example.com',
+ 'password' => 'secret',
+ ]);
+
+ $email = Email::compose()
+ ->user($user)
+ ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey'))
+ ->content(fn (Content $content) => $content->view('welcome'))
+ ->send();
+
+ $this->assertNull($email->model_type);
+ $this->assertNull($email->model_id);
+ }
+}
diff --git a/tests/ConfigCacheTest.php b/tests/ConfigCacheTest.php
index 2851e6b..5c04075 100644
--- a/tests/ConfigCacheTest.php
+++ b/tests/ConfigCacheTest.php
@@ -2,18 +2,19 @@
namespace Tests;
+use PHPUnit\Framework\Attributes\Test;
use Throwable;
class ConfigCacheTest extends TestCase
{
- /** @test */
+ #[Test]
public function the_configuration_file_can_be_cached()
{
$failed = false;
try {
- serialize(require __DIR__ . '/../config/laravel-database-emails.php');
- } catch (Throwable $e) {
+ serialize(require __DIR__.'/../config/database-emails.php');
+ } catch (Throwable) {
$failed = true;
}
diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php
index e6c376d..27bbba0 100644
--- a/tests/ConfigTest.php
+++ b/tests/ConfigTest.php
@@ -2,56 +2,47 @@
namespace Tests;
+use PHPUnit\Framework\Attributes\Test;
use Stackkit\LaravelDatabaseEmails\Config;
class ConfigTest extends TestCase
{
- /** @test */
+ #[Test]
public function test_max_attempt_count()
{
$this->assertEquals(3, Config::maxAttemptCount());
- $this->app['config']->set('laravel-database-emails.attempts', 5);
+ $this->app['config']->set('database-emails.attempts', 5);
$this->assertEquals(5, Config::maxAttemptCount());
}
- /** @test */
- public function test_encrypt_emails()
- {
- $this->assertFalse(Config::encryptEmails());
-
- $this->app['config']->set('laravel-database-emails.encrypt', true);
-
- $this->assertTrue(Config::encryptEmails());
- }
-
- /** @test */
+ #[Test]
public function test_testing()
{
$this->assertFalse(Config::testing());
- $this->app['config']->set('laravel-database-emails.testing.enabled', true);
+ $this->app['config']->set('database-emails.testing.enabled', true);
$this->assertTrue(Config::testing());
}
- /** @test */
+ #[Test]
public function test_test_email_address()
{
$this->assertEquals('test@email.com', Config::testEmailAddress());
- $this->app['config']->set('laravel-database-emails.testing.email', 'test+update@email.com');
+ $this->app['config']->set('database-emails.testing.email', 'test+update@email.com');
$this->assertEquals('test+update@email.com', Config::testEmailAddress());
}
- /** @test */
+ #[Test]
public function test_cronjob_email_limit()
{
$this->assertEquals(20, Config::cronjobEmailLimit());
- $this->app['config']->set('laravel-database-emails.limit', 15);
+ $this->app['config']->set('database-emails.limit', 15);
$this->assertEquals(15, Config::cronjobEmailLimit());
}
diff --git a/tests/DatabaseInteractionTest.php b/tests/DatabaseInteractionTest.php
index 1bc8d71..11fdb6d 100644
--- a/tests/DatabaseInteractionTest.php
+++ b/tests/DatabaseInteractionTest.php
@@ -3,32 +3,35 @@
namespace Tests;
use Carbon\Carbon;
+use Illuminate\Mail\Mailables\Address;
use Illuminate\Support\Facades\DB;
+use PHPUnit\Framework\Attributes\Test;
+use Stackkit\LaravelDatabaseEmails\Attachment;
class DatabaseInteractionTest extends TestCase
{
- /** @test */
+ #[Test]
public function label_should_be_saved_correctly()
{
$email = $this->sendEmail(['label' => 'welcome-email']);
$this->assertEquals('welcome-email', DB::table('emails')->find(1)->label);
- $this->assertEquals('welcome-email', $email->getLabel());
+ $this->assertEquals('welcome-email', $email->label);
}
- /** @test */
+ #[Test]
public function recipient_should_be_saved_correctly()
{
$email = $this->sendEmail(['recipient' => 'john@doe.com']);
- $this->assertEquals('john@doe.com', $email->getRecipient());
+ $this->assertEquals(['john@doe.com' => null], $email->recipient);
}
- /** @test */
+ #[Test]
public function cc_and_bcc_should_be_saved_correctly()
{
$email = $this->sendEmail([
- 'cc' => $cc = [
+ 'cc' => $cc = [
'john@doe.com',
],
'bcc' => $bcc = [
@@ -36,76 +39,54 @@ public function cc_and_bcc_should_be_saved_correctly()
],
]);
- $this->assertEquals(json_encode($cc), DB::table('emails')->find(1)->cc);
- $this->assertTrue($email->hasCc());
- $this->assertEquals(['john@doe.com'], $email->getCc());
- $this->assertEquals(json_encode($bcc), DB::table('emails')->find(1)->bcc);
- $this->assertTrue($email->hasBcc());
- $this->assertEquals(['jane@doe.com'], $email->getBcc());
+ $this->assertEquals(['john@doe.com' => null], $email->cc);
+ $this->assertEquals(['jane@doe.com' => null], $email->bcc);
}
- /** @test */
+ #[Test]
public function reply_to_should_be_saved_correctly()
{
$email = $this->sendEmail([
- 'reply_to' => $replyTo = [
+ 'reply_to' => [
'john@doe.com',
],
]);
- $this->assertEquals(json_encode($replyTo), DB::table('emails')->find(1)->reply_to);
- $this->assertTrue($email->hasReplyTo());
- $this->assertEquals(['john@doe.com'], $email->getReplyTo());
+ $this->assertEquals(['john@doe.com' => null], $email->reply_to);
}
- /** @test */
+ #[Test]
public function subject_should_be_saved_correclty()
{
$email = $this->sendEmail(['subject' => 'test subject']);
$this->assertEquals('test subject', DB::table('emails')->find(1)->subject);
- $this->assertEquals('test subject', $email->getSubject());
+ $this->assertEquals('test subject', $email->subject);
}
- /** @test */
+ #[Test]
public function view_should_be_saved_correctly()
{
$email = $this->sendEmail(['view' => 'tests::dummy']);
$this->assertEquals('tests::dummy', DB::table('emails')->find(1)->view);
- $this->assertEquals('tests::dummy', $email->getView());
+ $this->assertEquals('tests::dummy', $email->view);
}
- /** @test */
- public function encrypted_should_be_saved_correctly()
- {
- $email = $this->sendEmail();
-
- $this->assertEquals(0, DB::table('emails')->find(1)->encrypted);
- $this->assertFalse($email->isEncrypted());
-
- $this->app['config']['laravel-database-emails.encrypt'] = true;
-
- $email = $this->sendEmail();
-
- $this->assertEquals(1, DB::table('emails')->find(2)->encrypted);
- $this->assertTrue($email->isEncrypted());
- }
-
- /** @test */
+ #[Test]
public function scheduled_date_should_be_saved_correctly()
{
$email = $this->sendEmail();
$this->assertNull(DB::table('emails')->find(1)->scheduled_at);
- $this->assertNull($email->getScheduledDate());
+ $this->assertNull($email->scheduled_at);
Carbon::setTestNow(Carbon::create(2019, 1, 1, 1, 2, 3));
$email = $this->scheduleEmail('+2 weeks');
$this->assertNotNull(DB::table('emails')->find(2)->scheduled_at);
- $this->assertEquals('2019-01-15 01:02:03', $email->getScheduledDate());
+ $this->assertEquals('2019-01-15 01:02:03', $email->scheduled_at);
}
- /** @test */
+ #[Test]
public function the_body_should_be_saved_correctly()
{
$email = $this->sendEmail(['variables' => ['name' => 'Jane Doe']]);
@@ -113,44 +94,44 @@ public function the_body_should_be_saved_correctly()
$expectedBody = "Name: Jane Doe\n";
$this->assertSame($expectedBody, DB::table('emails')->find(1)->body);
- $this->assertSame($expectedBody, $email->getBody());
+ $this->assertSame($expectedBody, $email->body);
}
- /** @test */
+ #[Test]
public function from_should_be_saved_correctly()
{
$email = $this->composeEmail()->send();
- $this->assertFalse($email->hasFrom());
- $this->assertEquals(config('mail.from.address'), $email->getFromAddress());
- $this->assertEquals(config('mail.from.name'), $email->getFromName());
+ $this->assertEquals($email->from['address'], $email->from['address']);
+ $this->assertEquals($email->from['name'], $email->from['name']);
- $email = $this->composeEmail()->from('marick@dolphiq.nl', 'Marick')->send();
+ $email = $this->composeEmail([
+ 'from' => new Address('marick@dolphiq.nl', 'Marick'),
+ ])->send();
- $this->assertTrue($email->hasFrom());
- $this->assertEquals('marick@dolphiq.nl', $email->getFromAddress());
- $this->assertEquals('Marick', $email->getFromName());
+ $this->assertTrue((bool) $email->from);
+ $this->assertEquals('marick@dolphiq.nl', $email->from['address']);
+ $this->assertEquals('Marick', $email->from['name']);
}
- /** @test */
+ #[Test]
public function variables_should_be_saved_correctly()
{
$email = $this->sendEmail(['variables' => ['name' => 'John Doe']]);
- $this->assertEquals(json_encode(['name' => 'John Doe'], 1), DB::table('emails')->find(1)->variables);
- $this->assertEquals(['name' => 'John Doe'], $email->getVariables());
+ $this->assertEquals(['name' => 'John Doe'], $email->variables);
}
- /** @test */
+ #[Test]
public function the_sent_date_should_be_null()
{
$email = $this->sendEmail();
$this->assertNull(DB::table('emails')->find(1)->sent_at);
- $this->assertNull($email->getSendDate());
+ $this->assertNull($email->sent_at);
}
- /** @test */
+ #[Test]
public function failed_should_be_zero()
{
$email = $this->sendEmail();
@@ -159,16 +140,16 @@ public function failed_should_be_zero()
$this->assertFalse($email->hasFailed());
}
- /** @test */
+ #[Test]
public function attempts_should_be_zero()
{
$email = $this->sendEmail();
$this->assertEquals(0, DB::table('emails')->find(1)->attempts);
- $this->assertEquals(0, $email->getAttempts());
+ $this->assertEquals(0, $email->attempts);
}
- /** @test */
+ #[Test]
public function the_scheduled_date_should_be_saved_correctly()
{
Carbon::setTestNow(Carbon::now());
@@ -177,62 +158,55 @@ public function the_scheduled_date_should_be_saved_correctly()
$email = $this->scheduleEmail('+2 weeks');
- $this->assertTrue($email->isScheduled());
- $this->assertEquals($scheduledFor, $email->getScheduledDate());
+ $this->assertEquals($scheduledFor, $email->scheduled_at);
}
- /** @test */
+ #[Test]
public function recipient_should_be_swapped_for_test_address_when_in_testing_mode()
{
- $this->app['config']->set('laravel-database-emails.testing.enabled', function () {
+ $this->app['config']->set('database-emails.testing.enabled', function () {
return true;
});
- $this->app['config']->set('laravel-database-emails.testing.email', 'test@address.com');
+ $this->app['config']->set('database-emails.testing.email', 'test@address.com');
$email = $this->sendEmail(['recipient' => 'jane@doe.com']);
- $this->assertEquals('test@address.com', $email->getRecipient());
+ $this->assertEquals(['test@address.com' => null], $email->recipient);
}
- /** @test */
+ #[Test]
public function attachments_should_be_saved_correctly()
{
$email = $this->composeEmail()
- ->attach(__DIR__ . '/files/pdf-sample.pdf')
- ->send();
-
- $this->assertCount(1, $email->getAttachments());
-
- $attachment = $email->getAttachments()[0];
-
- $this->assertEquals('attachment', $attachment['type']);
- $this->assertEquals(__DIR__ . '/files/pdf-sample.pdf', $attachment['attachment']['file']);
-
- $email = $this->composeEmail()
- ->attach(__DIR__ . '/files/pdf-sample.pdf')
- ->attach(__DIR__ . '/files/pdf-sample-2.pdf')
+ ->attachments([
+ Attachment::fromPath(__DIR__.'/files/pdf-sample.pdf'),
+ Attachment::fromPath(__DIR__.'/files/pdf-sample2.pdf'),
+ Attachment::fromStorageDisk('my-custom-disk', 'pdf-sample-2.pdf'),
+ ])
->send();
- $this->assertCount(2, $email->getAttachments());
+ $this->assertCount(3, $email->attachments);
- $this->assertEquals(__DIR__ . '/files/pdf-sample.pdf', $email->getAttachments()[0]['attachment']['file']);
- $this->assertEquals(__DIR__ . '/files/pdf-sample-2.pdf', $email->getAttachments()[1]['attachment']['file']);
+ $this->assertEquals(
+ [
+ 'path' => __DIR__.'/files/pdf-sample.pdf',
+ 'disk' => null,
+ 'as' => null,
+ 'mime' => null,
+ ],
+ $email->attachments[0]
+ );
}
- /** @test */
- public function in_memory_attachments_should_be_saved_correctly()
+ #[Test]
+ public function in_memory_attachments_are_not_supported()
{
- $rawData = file_get_contents(__DIR__ . '/files/pdf-sample.pdf');
+ $this->expectExceptionMessage('Raw attachments are not supported in the database email driver.');
- $email = $this->composeEmail()
- ->attachData($rawData, 'generated.pdf', [
- 'mime' => 'application/pdf',
+ $this->composeEmail()
+ ->attachments([
+ Attachment::fromData(fn () => file_get_contents(__DIR__.'/files/pdf-sample.pdf'), 'pdf-sample'),
])
->send();
-
- $this->assertCount(1, $email->getAttachments());
-
- $this->assertEquals('rawAttachment', $email->getAttachments()[0]['type']);
- $this->assertEquals(md5($rawData), md5($email->getAttachments()[0]['attachment']['data']));
}
}
diff --git a/tests/EncryptionTest.php b/tests/EncryptionTest.php
deleted file mode 100644
index 36a9328..0000000
--- a/tests/EncryptionTest.php
+++ /dev/null
@@ -1,133 +0,0 @@
-app['config']['laravel-database-emails.encrypt'] = true;
-
- $this->sendEmail();
- }
-
- /** @test */
- public function an_email_should_be_marked_as_encrypted()
- {
- $email = $this->sendEmail();
-
- $this->assertTrue($email->isEncrypted());
- }
-
- /** @test */
- public function the_recipient_should_be_encrypted_and_decrypted()
- {
- $email = $this->sendEmail(['recipient' => 'john@doe.com']);
-
- $this->assertEquals('john@doe.com', decrypt($email->getRawDatabaseValue('recipient')));
-
- $this->assertEquals('john@doe.com', $email->getRecipient());
- }
-
- /** @test */
- public function cc_and_bb_should_be_encrypted_and_decrypted()
- {
- $email = $this->sendEmail([
- 'cc' => $cc = ['john+1@doe.com', 'john+2@doe.com'],
- 'bcc' => $bcc = ['jane+1@doe.com', 'jane+2@doe.com'],
- ]);
-
- $this->assertEquals($cc, decrypt($email->getRawDatabaseValue('cc')));
- $this->assertEquals($bcc, decrypt($email->getRawDatabaseValue('bcc')));
-
- $this->assertEquals($cc, $email->getCc());
- $this->assertEquals($bcc, $email->getBcc());
- }
-
- /** @test */
- public function reply_to_should_be_encrypted_and_decrypted()
- {
- $email = $this->sendEmail([
- 'reply_to' => $replyTo = ['john+1@doe.com', 'john+2@doe.com'],
- ]);
- $this->assertEquals($replyTo, decrypt($email->getRawDatabaseValue('reply_to')));
- $this->assertEquals($replyTo, $email->getReplyTo());
-
- if (! class_exists(Address::class)) {
- return;
- }
-
- // Test with a single Address object...
- $email = $this->sendEmail([
- 'reply_to' => new Address('john+1@doe.com', 'John Doe'),
- ]);
- $this->assertEquals([['address' => 'john+1@doe.com', 'name' => 'John Doe']], decrypt($email->getRawDatabaseValue('reply_to')));
- $this->assertEquals([['address' => 'john+1@doe.com', 'name' => 'John Doe']], $email->getReplyTo());
-
- // Address with an array of Address objects...
- $email = $this->sendEmail([
- 'reply_to' => [
- new Address('john+1@doe.com', 'John Doe'),
- new Address('jane+1@doe.com', 'Jane Doe'),
- ],
- ]);
- $this->assertSame([['address' => 'john+1@doe.com', 'name' => 'John Doe'], ['address' => 'jane+1@doe.com', 'name' => 'Jane Doe']], decrypt($email->getRawDatabaseValue('reply_to')));
- $this->assertSame([['address' => 'john+1@doe.com', 'name' => 'John Doe'], ['address' => 'jane+1@doe.com', 'name' => 'Jane Doe']], $email->getReplyTo());
- }
-
- /** @test */
- public function the_subject_should_be_encrypted_and_decrypted()
- {
- $email = $this->sendEmail(['subject' => 'test subject']);
-
- $this->assertEquals('test subject', decrypt($email->getRawDatabaseValue('subject')));
-
- $this->assertEquals('test subject', $email->getSubject());
- }
-
- /** @test */
- public function the_variables_should_be_encrypted_and_decrypted()
- {
- $email = $this->sendEmail(['variables' => ['name' => 'Jane Doe']]);
-
- $this->assertEquals(
- ['name' => 'Jane Doe'],
- decrypt($email->getRawDatabaseValue('variables'))
- );
-
- $this->assertEquals(
- ['name' => 'Jane Doe'],
- $email->getVariables()
- );
- }
-
- /** @test */
- public function the_body_should_be_encrypted_and_decrypted()
- {
- $email = $this->sendEmail(['variables' => ['name' => 'Jane Doe']]);
-
- $expectedBody = "Name: Jane Doe\n";
-
- $this->assertEquals($expectedBody, decrypt($email->getRawDatabaseValue('body')));
-
- $this->assertEquals($expectedBody, $email->getBody());
- }
-
- /** @test */
- public function from_should_be_encrypted_and_decrypted()
- {
- $email = $this->composeEmail()->from('marick@dolphiq.nl', 'Marick')->send();
-
- $expect = [
- 'address' => 'marick@dolphiq.nl',
- 'name' => 'Marick',
- ];
-
- $this->assertEquals($expect, decrypt($email->getRawDatabaseValue('from')));
- $this->assertEquals($expect, $email->getFrom());
- }
-}
diff --git a/tests/EnvelopeTest.php b/tests/EnvelopeTest.php
new file mode 100644
index 0000000..4e29bc1
--- /dev/null
+++ b/tests/EnvelopeTest.php
@@ -0,0 +1,134 @@
+envelope(
+ (new Envelope())
+ ->subject('Hey')
+ ->from('asdf@gmail.com')
+ ->to(['johndoe@example.com', 'janedoe@example.com'])
+ )
+ ->content(
+ (new Content())
+ ->view('tests::dummy')
+ ->with(['name' => 'John Doe'])
+ )
+ ->send();
+
+ $this->assertEquals([
+ 'johndoe@example.com' => null,
+ 'janedoe@example.com' => null,
+ ], $email->recipient);
+ }
+
+ #[Test]
+ public function test_it_can_pass_user_models()
+ {
+ $user = (new User())->forceFill([
+ 'email' => 'johndoe@example.com',
+ 'name' => 'J. Doe',
+ ]);
+
+ $email = Email::compose()
+ ->user($user)
+ ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey'))
+ ->content(fn (Content $content) => $content->view('welcome'))
+ ->send();
+
+ $this->assertEquals(
+ [
+ 'johndoe@example.com' => 'J. Doe',
+ ],
+ $email->recipient
+ );
+ }
+
+ #[Test]
+ public function users_can_have_a_preferred_email()
+ {
+ $user = (new UserWithPreferredEmail())->forceFill([
+ 'email' => 'johndoe@example.com',
+ 'name' => 'J. Doe',
+ ]);
+
+ $email = Email::compose()
+ ->user($user)
+ ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey'))
+ ->content(fn (Content $content) => $content->view('welcome'))
+ ->send();
+
+ $this->assertEquals(
+ [
+ 'noreply@abc.com' => 'J. Doe',
+ ],
+ $email->recipient
+ );
+ }
+
+ #[Test]
+ public function users_can_have_a_preferred_name()
+ {
+ $user = (new UserWithPreferredName())->forceFill([
+ 'email' => 'johndoe@example.com',
+ 'name' => 'J. Doe',
+ ]);
+
+ $email = Email::compose()
+ ->user($user)
+ ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey'))
+ ->content(fn (Content $content) => $content->view('welcome'))
+ ->send();
+
+ $this->assertEquals(
+ [
+ 'johndoe@example.com' => 'J.D.',
+ ],
+ $email->recipient
+ );
+ }
+
+ #[Test]
+ public function users_can_have_a_preferred_locale()
+ {
+ $nonLocaleUser = (new User())->forceFill([
+ 'email' => 'johndoe@example.com',
+ 'name' => 'J. Doe',
+ ]);
+
+ $emailForNonLocaleUser = Email::compose()
+ ->user($nonLocaleUser)
+ ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey'))
+ ->content(fn (Content $content) => $content->view('locale-email'))
+ ->send();
+
+ $localeUser = (new UserWithPreferredLocale())->forceFill([
+ 'email' => 'johndoe@example.com',
+ 'name' => 'J. Doe',
+ ]);
+
+ $emailForLocaleUser = Email::compose()
+ ->user($localeUser)
+ ->envelope(fn (Envelope $envelope) => $envelope->subject('Hey'))
+ ->content(fn (Content $content) => $content->view('locale-email'))
+ ->send();
+
+ $this->assertStringContainsString('Hello!', $emailForNonLocaleUser->body);
+ $this->assertStringContainsString('Kumusta!', $emailForLocaleUser->body);
+ }
+}
diff --git a/tests/MailableReaderTest.php b/tests/MailableReaderTest.php
index c8fbd11..7e15865 100644
--- a/tests/MailableReaderTest.php
+++ b/tests/MailableReaderTest.php
@@ -4,98 +4,90 @@
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Address;
-use Illuminate\Mail\Mailables\Attachment;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
+use PHPUnit\Framework\Attributes\Test;
+use Stackkit\LaravelDatabaseEmails\Attachment;
use Stackkit\LaravelDatabaseEmails\Email;
class MailableReaderTest extends TestCase
{
private function mailable(): Mailable
{
- if (version_compare(app()->version(), '10.0.0', '>=')) {
- return new Laravel10TestMailable();
- }
-
return new TestMailable();
}
- /** @test */
+ #[Test]
public function it_extracts_the_recipient()
{
$composer = Email::compose()
->mailable($this->mailable());
- $this->assertEquals(['john@doe.com'], $composer->getData('recipient'));
+ $this->assertEquals(['john@doe.com' => 'John Doe'], $composer->email->recipient);
$composer = Email::compose()
->mailable(
$this->mailable()->to(['jane@doe.com'])
);
- $this->assertCount(2, $composer->getData('recipient'));
- $this->assertContains('john@doe.com', $composer->getData('recipient'));
- $this->assertContains('jane@doe.com', $composer->getData('recipient'));
+ $this->assertCount(2, $composer->email->recipient);
+ $this->assertArrayHasKey('john@doe.com', $composer->email->recipient);
+ $this->assertArrayHasKey('jane@doe.com', $composer->email->recipient);
}
- /** @test */
+ #[Test]
public function it_extracts_cc_addresses()
{
$composer = Email::compose()->mailable($this->mailable());
- $this->assertEquals(['john+cc@doe.com', 'john+cc2@doe.com'], $composer->getData('cc'));
+ $this->assertEquals(['john+cc@doe.com' => null, 'john+cc2@doe.com' => null], $composer->email->cc);
}
- /** @test */
+ #[Test]
public function it_extracts_bcc_addresses()
{
$composer = Email::compose()->mailable($this->mailable());
- $this->assertEquals(['john+bcc@doe.com', 'john+bcc2@doe.com'], $composer->getData('bcc'));
+ $this->assertEquals(['john+bcc@doe.com' => null, 'john+bcc2@doe.com' => null], $composer->email->bcc);
}
- /** @test */
+ #[Test]
public function it_extracts_reply_to_addresses()
{
$composer = Email::compose()->mailable($this->mailable());
- $this->assertEquals(['replyto@example.com', 'replyto2@example.com'], $composer->getData('reply_to'));
+ $this->assertEquals(['replyto@example.com' => null, 'replyto2@example.com' => null], $composer->email->reply_to);
}
- /** @test */
+ #[Test]
public function it_extracts_the_subject()
{
$composer = Email::compose()->mailable($this->mailable());
- $this->assertEquals('Your order has shipped!', $composer->getData('subject'));
+ $this->assertEquals('Your order has shipped!', $composer->email->subject);
}
- /** @test */
+ #[Test]
public function it_extracts_the_body()
{
$composer = Email::compose()->mailable($this->mailable());
- $this->assertEquals("Name: John Doe\n", $composer->getData('body'));
+ $this->assertEquals("Name: John Doe\n", $composer->email->body);
}
- /** @test */
+ #[Test]
public function it_extracts_attachments()
{
$email = Email::compose()->mailable($this->mailable())->send();
- $attachments = $email->getAttachments();
+ $attachments = $email->attachments;
$this->assertCount(2, $attachments);
- $this->assertEquals('attachment', $attachments[0]['type']);
- $this->assertEquals(__DIR__ . '/files/pdf-sample.pdf', $attachments[0]['attachment']['file']);
-
- $this->assertEquals('rawAttachment', $attachments[1]['type']);
- $this->assertEquals('order.html', $attachments[1]['attachment']['name']);
- $this->assertEquals('