├── .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 |
--------------------------------------------------------------------------------