├── .gitignore
├── stubs
└── migrations
│ └── AddDigestedToNotificationsTable.php.stub
├── composer.json
├── LICENSE
├── src
├── Commands
│ └── GenerateDigestEmails.php
├── Notifications
│ └── SimpleDigestifEmail.php
├── DigestifServiceProvider.php
├── config
│ └── digestif.php
└── Tasks
│ └── Digestable.php
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 |
--------------------------------------------------------------------------------
/stubs/migrations/AddDigestedToNotificationsTable.php.stub:
--------------------------------------------------------------------------------
1 | dateTime('digested_at')->nullable();
21 | });
22 |
23 | }
24 | }
25 |
26 | /**
27 | * Reverse the migrations.
28 | *
29 | * @return void
30 | */
31 | public function down()
32 | {
33 | if( Schema::hasTable('notifications') ) {
34 |
35 | Schema::table('notifications', function (Blueprint $table) {
36 | $table->dropColumn('digested_at');
37 | });
38 |
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "codepotatoltd/digestif",
3 | "description": "A digest email for Laravel database notifications",
4 | "version": "0.2.0",
5 | "type": "library",
6 | "require": {
7 | "php": ">=7.4",
8 | "illuminate/console": "^6.0 || ^7.0 || ^8.0",
9 | "illuminate/support": "^6.0 || ^7.0 || ^8.0",
10 | "illuminate/bus": "^6.0 || ^7.0 || ^8.0",
11 | "illuminate/notifications": "^6.0 || ^7.0 || ^8.0",
12 | "illuminate/database": "^6.0 || ^7.0 || ^8.0"
13 | },
14 | "license": "MIT",
15 | "authors": [
16 | {
17 | "name": "Gareth Thompson",
18 | "email": "gareth@codepotato.co.uk"
19 | }
20 | ],
21 | "minimum-stability": "dev",
22 | "autoload": {
23 | "psr-4": {
24 | "CodepotatoLtd\\Digestif\\": "src/"
25 | }
26 | },
27 | "extra": {
28 | "laravel": {
29 | "providers": [
30 | "CodepotatoLtd\\Digestif\\DigestifServiceProvider"
31 | ]
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Codepotato Ltd
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 |
--------------------------------------------------------------------------------
/src/Commands/GenerateDigestEmails.php:
--------------------------------------------------------------------------------
1 | error('Zoiks! Digestif is disabled');
39 | return 0;
40 | }
41 |
42 | if( !Schema::hasTable('notifications') ){
43 | $this->error('We can\'t see a notifications table. Perhaps try running "php artisan notifications:table"');
44 | return 0;
45 | }
46 |
47 | $this->info('Pouring a small one to wet the whistle.');
48 |
49 | $service = new Digestable($this);
50 | $service->run();
51 |
52 | return 0;
53 |
54 | }
55 |
56 | }
--------------------------------------------------------------------------------
/src/Notifications/SimpleDigestifEmail.php:
--------------------------------------------------------------------------------
1 | notification_count = $notification_count;
24 | }
25 |
26 | /**
27 | * Get the notification's delivery channels.
28 | *
29 | * @param mixed $notifiable
30 | * @return array
31 | */
32 | public function via($notifiable)
33 | {
34 | return ['mail'];
35 | }
36 |
37 | /**
38 | * Get the mail representation of the notification.
39 | *
40 | * @param mixed $notifiable
41 | * @return \Illuminate\Notifications\Messages\MailMessage
42 | */
43 | public function toMail($notifiable)
44 | {
45 | return (new MailMessage)
46 | ->greeting('Hello!')
47 | ->line('We just thought you might like to know that you have ' . $this->notification_count . ' unread ' . Str::plural('notification', $this->notification_count) . ' in your account')
48 | ->action('View notifications', url('/'))
49 | ->line('Thank you for using our application!');
50 | }
51 | }
--------------------------------------------------------------------------------
/src/DigestifServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->runningInConsole()) {
15 | $this->registerCommands()
16 | ->offerPublishing();
17 | }
18 |
19 | }
20 |
21 | /**
22 | * @return $this
23 | */
24 | protected function registerCommands(): self
25 | {
26 | $this->commands([
27 | GenerateDigestEmails::class,
28 | ]);
29 |
30 | return $this;
31 | }
32 |
33 |
34 | /**
35 | * @return $this
36 | */
37 | protected function offerPublishing(): self
38 | {
39 | $this->publishes([
40 | __DIR__.'/config/digestif.php' => config_path('digestif.php'),
41 | ], 'digestif-config');
42 |
43 | $this->publishes([__DIR__ . '/Notifications/SimpleDigestifEmail.php' => app_path('Notifications/SimpleDigestifEmail.php')], 'digestif-notifications');
44 |
45 | if ($this->app->runningInConsole()) {
46 | if (!class_exists('AddDigestedToNotificationsTable')) {
47 | $this->publishes([
48 | __DIR__ . '/../stubs/migrations/AddDigestedToNotificationsTable.php.stub' => database_path('migrations/' . date('Y_m_d_His', time()) . '_add_digested_to_notifications_table.php'),
49 | ], 'digestif-migrations');
50 | }
51 | }
52 |
53 | return $this;
54 | }
55 |
56 |
57 | public function register(){
58 | $this->mergeConfigFrom(
59 | __DIR__ . '/config/digestif.php',
60 | 'digestif'
61 | );
62 | }
63 |
64 |
65 | }
--------------------------------------------------------------------------------
/src/config/digestif.php:
--------------------------------------------------------------------------------
1 | true,
14 |
15 | /*
16 | |--------------------------------------------------------------------------
17 | | Digestif email type
18 | |--------------------------------------------------------------------------
19 | |
20 | | In the future there will be more than one email type that digestif can
21 | | send. For now leave this as 'simple'
22 | |
23 | */
24 | 'type' => 'simple',
25 |
26 | /*
27 | |--------------------------------------------------------------------------
28 | | Digestif user model class
29 | |--------------------------------------------------------------------------
30 | |
31 | | Please set the model used for your users. Note, they must have the
32 | | notifiable trait.
33 | |
34 | */
35 | 'user_model' => \User::class,
36 |
37 | /*
38 | |--------------------------------------------------------------------------
39 | | Notifications table user ID column
40 | |--------------------------------------------------------------------------
41 | |
42 | | Please tell us what column stores the user_id used for notifications
43 | |
44 | */
45 | 'notifications_user_id_column' => 'notifiable_id',
46 |
47 |
48 | /*
49 | |--------------------------------------------------------------------------
50 | | Notifications read column
51 | |--------------------------------------------------------------------------
52 | |
53 | | Please tell us what column stores the read toggle for notifications
54 | |
55 | */
56 | 'read_column' => 'read_at',
57 |
58 | ];
--------------------------------------------------------------------------------
/src/Tasks/Digestable.php:
--------------------------------------------------------------------------------
1 | awaiting_digest = [];
27 | $this->artisan = $artisan;
28 | }
29 |
30 | /**
31 | *
32 | */
33 | public function run()
34 | {
35 | // check to see if the table column exists to power our digests
36 | if (!$this->preFlightChecks()) {
37 | $this->artisan->error('Digestive requires a migrate, please');
38 | }
39 |
40 | $user_column = \config('digestif.notifications_user_id_column', 'notifiable_id');
41 |
42 | DB::table('notifications')
43 | ->orderBy('id')
44 | ->select('*')
45 | ->where( \config('digestif.read_column'), '=', null)
46 | ->where('digested_at', '=', null)
47 | ->chunk(100, function ($notifications) use ($user_column) {
48 |
49 | foreach ($notifications as $notification) {
50 | if (!isset($this->awaiting_digest[$notification->{$user_column}])) {
51 | $this->awaiting_digest[$notification->{$user_column}] = 1;
52 | DB::table('notifications')->where('id', $notification->id)->update(['digested_at' => now()]);
53 | continue;
54 | }
55 | $this->awaiting_digest[$notification->{$user_column}]++;
56 | DB::table('notifications')->where('id', $notification->id)->update(['digested_at' => now()]);
57 | }
58 | });
59 |
60 | $this->artisan->info('Preparing a total of ' . count($this->awaiting_digest) . ' digestifs for your users');
61 |
62 | if (count($this->awaiting_digest)) {
63 | foreach( $this->awaiting_digest as $key => $count ){
64 | // process the notification for each user
65 |
66 | $model = \config('digestif.user_model');
67 | $user = (app($model))::find($key);
68 |
69 | if( $user instanceof $model ) {
70 | $user->notify(new \App\Notifications\SimpleDigestifEmail($count));
71 | $this->artisan->info('Poured a ' . $this->getDigestifName() . ' for '. $user->email );
72 | }
73 |
74 | }
75 | $this->artisan->info('Hic. I think we\'ve had enough for right now. ');
76 | } else {
77 | $this->artisan->info('Alas, there was no need for a digestif. More for us! Hic!');
78 | }
79 |
80 | }
81 |
82 | /**
83 | * @return bool
84 | */
85 | final private function preFlightChecks()
86 | {
87 | return Schema::hasColumn('notifications', 'digested_at');
88 | }
89 |
90 | /**
91 | * @return array|mixed
92 | */
93 | final private function getDigestifName(){
94 | return Arr::random($this->drinks);
95 | }
96 |
97 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🥃 Digestif - Gloriously simple digest emails for Laravel
2 |
3 |
4 |
5 |
6 |
7 | ## What is this?
8 | Using the database driver for laravel notifications is great, but then you need a way to communicate to your users if they've not logged in for a while. With Digestif we'll handle the digest emails for you.
9 |
10 | ## Installation instructions
11 | 1. Install using ```composer require codepotatoltd/digestif```
12 | 2. Ensure you have a notifications table in your database, otherwise run ```php artisan notifications:table``` and then ```php artisan migrate```
13 | 2. Publish Digestif's config, notification and database migration using ```php artisan vendor:publish``` and select the DigestifServiceProvider option.
14 | 4. Run ```php artisan migrate``` to add the "digested_at" column
15 | 5. Open the digestif.php config file and update the User model that your app uses.
16 | 6. Open your App/Console/Kernel.php and in the scheduler define how often you would like the digest process to run. E.g. ```$schedule->command('digestif:pour')->hourly();``` Alternatively, setup your own cron process to run ```php artisan digestif:pour``` when you would like emails to be sent.
17 | 7. Sit back and let Digestif handle the rest 🥃
18 |
19 | ## Config options
20 | Using the vendor:publish method you have full control of both the notification that we use behind the scenes. You can find this in your App/Notifications folder and you're welcome to tweak this to something that works better for you.
21 |
22 | For example:
23 |
24 | | Config Variable | Default | Description
25 | |---|---|---|
26 | | enabled | true (boolean) | Should Digestif run or not? |
27 | | type | simple (string) | What variety of email should it send. Only one option at the moment |
28 | | user_model | User::class | What model is used for users that should receive the digest email |
29 | | notifications_user_id_column | notifiable_id (string) | What column on the notifications table stores the user_id |
30 | | read_column | read_at | What column from the notifications table stores whether the notification has been read or not |
31 |
32 |
33 | ## Roadmap
34 | - [x] ~~Simple digest email~~
35 | - [ ] Refactor to filter out user notifications only
36 | - [ ] Unsubscribing from the digest email
37 | - [ ] Itemised digest emails as well as our simple counter version
38 | - [ ] User controls to set the frequency of their digests
39 |
40 |
41 | ## Upgrade Guide
42 |
43 | ### To v0.20 from v0.10
44 |
45 | Add the following to your digestif.php config file:
46 |
47 | ```
48 | /*
49 | |--------------------------------------------------------------------------
50 | | Notifications read column
51 | |--------------------------------------------------------------------------
52 | |
53 | | Please tell us what column stores the read toggle for notifications
54 | |
55 | */
56 | 'read_column' => 'read_at',
57 | ```
58 |
59 | ### To v0.10 from v0.0.2
60 |
61 | Add the following to your digestif.php config file
62 |
63 | ```composer log
64 | /*
65 | |--------------------------------------------------------------------------
66 | | Notifications table user ID column
67 | |--------------------------------------------------------------------------
68 | |
69 | | Please tell us what column stores the user_id used for notifications
70 | |
71 | */
72 | 'notifications_user_id_column' => 'notifiable_id',
73 | ```
--------------------------------------------------------------------------------