├── .github └── workflows │ └── master.yml ├── .gitignore ├── .styleci.yml ├── LICENSE.md ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── ShvetsGroup │ └── LaravelEmailDatabaseLog │ │ ├── EmailLogger.php │ │ ├── LaravelEmailDatabaseLogEventServiceProvider.php │ │ └── LaravelEmailDatabaseLogServiceProvider.php └── database │ └── migrations │ ├── 2015_07_31_1_email_log.php │ ├── 2016_09_21_001638_add_bcc_column_email_log.php │ ├── 2017_11_10_001638_add_more_mail_columns_email_log.php │ └── 2018_05_11_115355_use_longtext_for_attachments.php └── tests ├── Feature └── LaravelEmailDatabaseTest.php ├── Mail ├── TestMail.php └── TestMailWithAttachment.php ├── TestCase.php └── stubs └── demo.txt /.github/workflows/master.yml: -------------------------------------------------------------------------------- 1 | name: run-tests 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | test: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | fail-fast: true 14 | matrix: 15 | os: [ubuntu-latest, windows-latest] 16 | php: [8.0, 8.1] 17 | laravel: [9.*] 18 | stability: [prefer-lowest, prefer-stable] 19 | include: 20 | - laravel: 9.* 21 | testbench: ^7.0 22 | 23 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} 24 | 25 | steps: 26 | - name: Checkout code 27 | uses: actions/checkout@v2 28 | 29 | - name: Setup PHP 30 | uses: shivammathur/setup-php@v2 31 | with: 32 | php-version: ${{ matrix.php }} 33 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo 34 | coverage: none 35 | 36 | - name: Setup problem matchers 37 | run: | 38 | echo "::add-matcher::${{ runner.tool_cache }}/php.json" 39 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 40 | - name: Install dependencies 41 | run: | 42 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update 43 | composer update --${{ matrix.stability }} --prefer-dist --no-interaction 44 | - name: Execute tests 45 | run: vendor/bin/phpunit 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store 5 | .phpunit.result.cache 6 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: laravel 2 | 3 | disabled: 4 | - single_class_element_per_statement 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Alexander Shvets 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Email Database Log 2 | 3 | A simple database logger for all outgoing emails sent by Laravel website. 4 | 5 | # Installation 6 | 7 | ## Step 1: Composer 8 | 9 | Laravel Email Database Log can be installed via [composer](http://getcomposer.org) by running this line in terminal: 10 | 11 | ```bash 12 | composer require shvetsgroup/laravel-email-database-log 13 | ``` 14 | 15 | ## Step 2: Configuration 16 | 17 | You can skip this step if your version of Laravel is 5.5 or above. Otherwise, you have to add the following to your config/app.php in the providers array: 18 | 19 | ```php 20 | 'providers' => [ 21 | // ... 22 | ShvetsGroup\LaravelEmailDatabaseLog\LaravelEmailDatabaseLogServiceProvider::class, 23 | ], 24 | ``` 25 | 26 | ## Step 3: Migration 27 | 28 | Publish migration files: 29 | ```bash 30 | php artisan vendor:publish --tag=laravel-email-database-log-migration 31 | ``` 32 | 33 | Now, run this in terminal: 34 | ```bash 35 | php artisan migrate 36 | ``` 37 | 38 | # Usage 39 | 40 | After installation, any email sent by your website will be logged to `email_log` table in the site's database. 41 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shvetsgroup/laravel-email-database-log", 3 | "description": "A simple database logger for all outgoing emails sent by Laravel website.", 4 | "keywords": ["laravel", "markdown", "mail"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Alexander Shvets", 9 | "email": "neochief@shvetsgroup.com" 10 | }, 11 | { 12 | "name": "Spaan Productions", 13 | "email": "info@spaanproductions.nl", 14 | "role": "Developer" 15 | } 16 | ], 17 | "require": { 18 | "php": "^8.0.2", 19 | "illuminate/support": "^9.0", 20 | "doctrine/dbal": "^3.3", 21 | "nesbot/carbon": "^2.0" 22 | }, 23 | "require-dev": { 24 | "orchestra/testbench": "^7.0", 25 | "phpunit/phpunit": "^9.0" 26 | }, 27 | "autoload": { 28 | "psr-0": { 29 | "ShvetsGroup\\LaravelEmailDatabaseLog\\": "src/" 30 | } 31 | }, 32 | "autoload-dev": { 33 | "psr-4": { 34 | "ShvetsGroup\\LaravelEmailDatabaseLog\\Tests\\": "tests" 35 | } 36 | }, 37 | "scripts": { 38 | "test": "vendor/bin/phpunit", 39 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage" 40 | }, 41 | "config": { 42 | "sort-packages": true 43 | }, 44 | "extra": { 45 | "laravel": { 46 | "providers": [ 47 | "ShvetsGroup\\LaravelEmailDatabaseLog\\LaravelEmailDatabaseLogServiceProvider" 48 | ] 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | src/ 19 | 20 | 21 | 22 | 25 | 26 | ./tests/Feature 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/ShvetsGroup/LaravelEmailDatabaseLog/EmailLogger.php: -------------------------------------------------------------------------------- 1 | message; 22 | 23 | DB::table('email_log')->insert([ 24 | 'date' => Carbon::now()->format('Y-m-d H:i:s'), 25 | 'from' => $this->formatAddressField($message, 'From'), 26 | 'to' => $this->formatAddressField($message, 'To'), 27 | 'cc' => $this->formatAddressField($message, 'Cc'), 28 | 'bcc' => $this->formatAddressField($message, 'Bcc'), 29 | 'subject' => $message->getSubject(), 30 | 'body' => $message->getBody()->bodyToString(), 31 | 'headers' => $message->getHeaders()->toString(), 32 | 'attachments' => $this->saveAttachments($message), 33 | ]); 34 | } 35 | 36 | /** 37 | * Format address strings for sender, to, cc, bcc. 38 | * 39 | * @param Email $message 40 | * @param string $field 41 | * @return null|string 42 | */ 43 | function formatAddressField(Email $message, string $field): ?string 44 | { 45 | $headers = $message->getHeaders(); 46 | 47 | return $headers->get($field)?->getBodyAsString(); 48 | } 49 | 50 | /** 51 | * Collect all attachments and format them as strings. 52 | * 53 | * @param Email $message 54 | * @return string|null 55 | */ 56 | protected function saveAttachments(Email $message): ?string 57 | { 58 | if (empty($message->getAttachments())) { 59 | return null; 60 | } 61 | 62 | return collect($message->getAttachments()) 63 | ->map(fn(DataPart $part) => $part->toString()) 64 | ->implode("\n\n"); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/ShvetsGroup/LaravelEmailDatabaseLog/LaravelEmailDatabaseLogEventServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 17 | EmailLogger::class, 18 | ], 19 | ]; 20 | } 21 | -------------------------------------------------------------------------------- /src/ShvetsGroup/LaravelEmailDatabaseLog/LaravelEmailDatabaseLogServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->register(LaravelEmailDatabaseLogEventServiceProvider::class); 27 | 28 | if ($this->app->runningInConsole()) { 29 | $this->publishes([ 30 | __DIR__ . '/../../database/migrations' => database_path('migrations'), 31 | ], 'laravel-email-database-log-migration'); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/database/migrations/2015_07_31_1_email_log.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->dateTime('date'); 18 | $table->string('from')->nullable(); 19 | $table->string('to')->nullable(); 20 | $table->string('cc')->nullable(); 21 | $table->string('bcc')->nullable(); 22 | $table->string('subject'); 23 | $table->text('body'); 24 | $table->text('headers')->nullable(); 25 | $table->text('attachments')->nullable(); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::drop('email_log'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/database/migrations/2016_09_21_001638_add_bcc_column_email_log.php: -------------------------------------------------------------------------------- 1 | string('to')->nullable()->change(); 17 | $table->string('bcc')->after('to')->nullable(); 18 | } 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | * 25 | * @return void 26 | */ 27 | public function down() 28 | { 29 | Schema::table('email_log', function ($table) { 30 | $table->string('to')->change(); 31 | $table->dropColumn('bcc'); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/database/migrations/2017_11_10_001638_add_more_mail_columns_email_log.php: -------------------------------------------------------------------------------- 1 | increments('id')->first(); 17 | $table->string('from')->after('date')->nullable(); 18 | $table->string('cc')->after('to')->nullable(); 19 | $table->text('headers')->after('body')->nullable(); 20 | $table->text('attachments')->after('headers')->nullable(); 21 | } 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::table('email_log', function ($table) { 33 | $table->dropColumn(['id', 'from', 'cc', 'headers', 'attachments']); 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/database/migrations/2018_05_11_115355_use_longtext_for_attachments.php: -------------------------------------------------------------------------------- 1 | longText('attachments')->nullable()->change(); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | * 22 | * @return void 23 | */ 24 | public function down() { 25 | Schema::table('email_log', function (Blueprint $table) { 26 | $table->text('attachments')->nullable()->change(); 27 | }); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /tests/Feature/LaravelEmailDatabaseTest.php: -------------------------------------------------------------------------------- 1 | send(new TestMail()); 22 | 23 | $this->assertDatabaseHas('email_log', [ 24 | 'date' => now()->format('Y-m-d H:i:s'), 25 | 'from' => 'Example ', 26 | 'to' => 'email@example.com', 27 | 'cc' => null, 28 | 'bcc' => null, 29 | 'subject' => 'The e-mail subject', 30 | 'body' => '

Some random string.

', 31 | 'attachments' => null, 32 | ]); 33 | } 34 | 35 | /** @test */ 36 | public function multiple_recipients_are_comma_separated() 37 | { 38 | Mail::to(['email@example.com', 'email2@example.com']) 39 | ->send(new TestMail()); 40 | 41 | $this->assertDatabaseHas('email_log', [ 42 | 'date' => now()->format('Y-m-d H:i:s'), 43 | 'to' => 'email@example.com, email2@example.com', 44 | 'cc' => null, 45 | 'bcc' => null, 46 | ]); 47 | } 48 | 49 | /** @test */ 50 | public function recipient_with_name_is_correctly_formatted() 51 | { 52 | Mail::to((object)['email' => 'email@example.com', 'name' => 'John Do']) 53 | ->send(new TestMail()); 54 | 55 | $this->assertDatabaseHas('email_log', [ 56 | 'date' => now()->format('Y-m-d H:i:s'), 57 | 'to' => 'John Do ', 58 | 'cc' => null, 59 | 'bcc' => null, 60 | ]); 61 | } 62 | 63 | /** @test */ 64 | public function cc_recipient_with_name_is_correctly_formatted() 65 | { 66 | Mail::cc((object)['email' => 'email@example.com', 'name' => 'John Do']) 67 | ->send(new TestMail()); 68 | 69 | $this->assertDatabaseHas('email_log', [ 70 | 'date' => now()->format('Y-m-d H:i:s'), 71 | 'to' => null, 72 | 'cc' => 'John Do ', 73 | 'bcc' => null, 74 | ]); 75 | } 76 | 77 | /** @test */ 78 | public function bcc_recipient_with_name_is_correctly_formatted() 79 | { 80 | Mail::bcc((object)['email' => 'email@example.com', 'name' => 'John Do']) 81 | ->send(new TestMail()); 82 | 83 | $this->assertDatabaseHas('email_log', [ 84 | 'date' => now()->format('Y-m-d H:i:s'), 85 | 'to' => null, 86 | 'cc' => null, 87 | 'bcc' => 'John Do ', 88 | ]); 89 | } 90 | 91 | /** @test */ 92 | public function attachement_is_saved() 93 | { 94 | Mail::to('email@example.com')->send(new TestMailWithAttachment()); 95 | 96 | $log = DB::table('email_log')->first(); 97 | 98 | // TODO: Is there a beter way to tests this ? 99 | $encoded = (new Base64Encoder)->encodeString(file_get_contents(__DIR__ . '/../stubs/demo.txt')); 100 | 101 | $this->assertStringContainsString('Content-Type: text/plain; name=demo.txt', $log->attachments); 102 | $this->assertStringContainsString('Content-Transfer-Encoding: base64', $log->attachments); 103 | $this->assertStringContainsString('Content-Disposition: attachment; name=demo.txt; filename=demo.txt', $log->attachments); 104 | $this->assertStringContainsString($encoded, $log->attachments); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /tests/Mail/TestMail.php: -------------------------------------------------------------------------------- 1 | subject('The e-mail subject') 33 | ->html('

Some random string.

'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Mail/TestMailWithAttachment.php: -------------------------------------------------------------------------------- 1 | subject('The e-mail subject') 33 | ->attach(__DIR__ . '/../stubs/demo.txt') 34 | ->html('

Some random string.

'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | up(); 19 | (new \AddBccColumnEmailLog)->up(); 20 | (new \AddMoreMailColumnsEmailLog)->up(); 21 | (new \UseLongtextForAttachments)->up(); 22 | } 23 | 24 | protected function getPackageProviders($app) 25 | { 26 | return [ 27 | LaravelEmailDatabaseLogServiceProvider::class, 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/stubs/demo.txt: -------------------------------------------------------------------------------- 1 | This is a demo attachment. 2 | --------------------------------------------------------------------------------