From 615741af9d36c725299cc8fc9c0ee012bd3d5759 Mon Sep 17 00:00:00 2001
From: Dan Brown <ssddanbrown@googlemail.com>
Date: Tue, 15 Aug 2023 14:39:39 +0100
Subject: [PATCH] Notifications: Cleaned up mails, added debounce for updates

- Updated mail notification design to be a bit prettier, and extracted
  text to new lang file for translation.
- Added debounce logic for page update notifications.
- Fixed watch options not being filtered to current user.
---
 app/Activity/Models/Comment.php               | 11 +++-----
 .../Handlers/BaseNotificationHandler.php      |  5 ++--
 .../CommentCreationNotificationHandler.php    | 11 ++++----
 .../Handlers/NotificationHandler.php          |  5 ++--
 .../PageCreationNotificationHandler.php       |  5 ++--
 .../PageUpdateNotificationHandler.php         | 22 ++++++++++++++--
 .../LinkedMailMessageLine.php                 |  2 +-
 .../MessageParts/ListMessageLine.php          | 26 +++++++++++++++++++
 .../Messages/BaseActivityNotification.php     | 13 ++++++++++
 .../Messages/CommentCreationNotification.php  | 22 +++++++---------
 .../Messages/PageCreationNotification.php     | 20 +++++++-------
 .../Messages/PageUpdateNotification.php       | 22 +++++++---------
 .../Notifications/NotificationManager.php     |  6 +++--
 app/Activity/Tools/ActivityLogger.php         |  2 +-
 app/Activity/Tools/UserEntityWatchOptions.php | 16 +++++++-----
 lang/en/notifications.php                     | 26 +++++++++++++++++++
 .../vendor/notifications/email.blade.php      |  8 +++---
 17 files changed, 152 insertions(+), 70 deletions(-)
 rename app/Activity/Notifications/{ => MessageParts}/LinkedMailMessageLine.php (91%)
 create mode 100644 app/Activity/Notifications/MessageParts/ListMessageLine.php
 create mode 100644 lang/en/notifications.php

diff --git a/app/Activity/Models/Comment.php b/app/Activity/Models/Comment.php
index 72098a3c3..bcbed6c56 100644
--- a/app/Activity/Models/Comment.php
+++ b/app/Activity/Models/Comment.php
@@ -5,6 +5,7 @@ namespace BookStack\Activity\Models;
 use BookStack\App\Model;
 use BookStack\Users\Models\HasCreatorAndUpdater;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
 use Illuminate\Database\Eloquent\Relations\MorphTo;
 
 /**
@@ -35,7 +36,7 @@ class Comment extends Model implements Loggable
     /**
      * Get the parent comment this is in reply to (if existing).
      */
-    public function parent()
+    public function parent(): BelongsTo
     {
         return $this->belongsTo(Comment::class);
     }
@@ -50,20 +51,16 @@ class Comment extends Model implements Loggable
 
     /**
      * Get created date as a relative diff.
-     *
-     * @return mixed
      */
-    public function getCreatedAttribute()
+    public function getCreatedAttribute(): string
     {
         return $this->created_at->diffForHumans();
     }
 
     /**
      * Get updated date as a relative diff.
-     *
-     * @return mixed
      */
-    public function getUpdatedAttribute()
+    public function getUpdatedAttribute(): string
     {
         return $this->updated_at->diffForHumans();
     }
diff --git a/app/Activity/Notifications/Handlers/BaseNotificationHandler.php b/app/Activity/Notifications/Handlers/BaseNotificationHandler.php
index e0b3f3ceb..f1742592e 100644
--- a/app/Activity/Notifications/Handlers/BaseNotificationHandler.php
+++ b/app/Activity/Notifications/Handlers/BaseNotificationHandler.php
@@ -2,6 +2,7 @@
 
 namespace BookStack\Activity\Notifications\Handlers;
 
+use BookStack\Activity\Models\Loggable;
 use BookStack\Activity\Notifications\Messages\BaseActivityNotification;
 use BookStack\Entities\Models\Entity;
 use BookStack\Permissions\PermissionApplicator;
@@ -18,7 +19,7 @@ abstract class BaseNotificationHandler implements NotificationHandler
      * @param class-string<BaseActivityNotification> $notification
      * @param int[] $userIds
      */
-    protected function sendNotificationToUserIds(string $notification, array $userIds, User $initiator, Entity $relatedModel): void
+    protected function sendNotificationToUserIds(string $notification, array $userIds, User $initiator, string|Loggable $detail, Entity $relatedModel): void
     {
         $users = User::query()->whereIn('id', array_unique($userIds))->get();
 
@@ -39,7 +40,7 @@ abstract class BaseNotificationHandler implements NotificationHandler
             }
 
             // Send the notification
-            $user->notify(new $notification($relatedModel, $initiator));
+            $user->notify(new $notification($detail, $initiator));
         }
     }
 }
diff --git a/app/Activity/Notifications/Handlers/CommentCreationNotificationHandler.php b/app/Activity/Notifications/Handlers/CommentCreationNotificationHandler.php
index 27d61307a..112852cf9 100644
--- a/app/Activity/Notifications/Handlers/CommentCreationNotificationHandler.php
+++ b/app/Activity/Notifications/Handlers/CommentCreationNotificationHandler.php
@@ -2,6 +2,7 @@
 
 namespace BookStack\Activity\Notifications\Handlers;
 
+use BookStack\Activity\Models\Activity;
 use BookStack\Activity\Models\Comment;
 use BookStack\Activity\Models\Loggable;
 use BookStack\Activity\Notifications\Messages\CommentCreationNotification;
@@ -12,7 +13,7 @@ use BookStack\Users\Models\User;
 
 class CommentCreationNotificationHandler extends BaseNotificationHandler
 {
-    public function handle(string $activityType, Loggable|string $detail, User $user): void
+    public function handle(Activity $activity, Loggable|string $detail, User $user): void
     {
         if (!($detail instanceof Comment)) {
             throw new \InvalidArgumentException("Detail for comment creation notifications must be a comment");
@@ -24,10 +25,10 @@ class CommentCreationNotificationHandler extends BaseNotificationHandler
         $watcherIds = $watchers->getWatcherUserIds();
 
         // Page owner if user preferences allow
-        if (!$watchers->isUserIgnoring($detail->owned_by) && $detail->ownedBy) {
-            $userNotificationPrefs = new UserNotificationPreferences($detail->ownedBy);
+        if (!$watchers->isUserIgnoring($detail->created_by) && $detail->createdBy) {
+            $userNotificationPrefs = new UserNotificationPreferences($detail->createdBy);
             if ($userNotificationPrefs->notifyOnOwnPageComments()) {
-                $watcherIds[] = $detail->owned_by;
+                $watcherIds[] = $detail->created_by;
             }
         }
 
@@ -40,6 +41,6 @@ class CommentCreationNotificationHandler extends BaseNotificationHandler
             }
         }
 
-        $this->sendNotificationToUserIds(CommentCreationNotification::class, $watcherIds, $user, $page);
+        $this->sendNotificationToUserIds(CommentCreationNotification::class, $watcherIds, $user, $detail, $page);
     }
 }
diff --git a/app/Activity/Notifications/Handlers/NotificationHandler.php b/app/Activity/Notifications/Handlers/NotificationHandler.php
index fecca2181..8c5498664 100644
--- a/app/Activity/Notifications/Handlers/NotificationHandler.php
+++ b/app/Activity/Notifications/Handlers/NotificationHandler.php
@@ -2,6 +2,7 @@
 
 namespace BookStack\Activity\Notifications\Handlers;
 
+use BookStack\Activity\Models\Activity;
 use BookStack\Activity\Models\Loggable;
 use BookStack\Users\Models\User;
 
@@ -9,8 +10,8 @@ interface NotificationHandler
 {
     /**
      * Run this handler.
-     * Provides the activity type, related activity detail/model
+     * Provides the activity, related activity detail/model
      * along with the user that triggered the activity.
      */
-    public function handle(string $activityType, string|Loggable $detail, User $user): void;
+    public function handle(Activity $activity, string|Loggable $detail, User $user): void;
 }
diff --git a/app/Activity/Notifications/Handlers/PageCreationNotificationHandler.php b/app/Activity/Notifications/Handlers/PageCreationNotificationHandler.php
index e9aca2f23..2492021e2 100644
--- a/app/Activity/Notifications/Handlers/PageCreationNotificationHandler.php
+++ b/app/Activity/Notifications/Handlers/PageCreationNotificationHandler.php
@@ -2,6 +2,7 @@
 
 namespace BookStack\Activity\Notifications\Handlers;
 
+use BookStack\Activity\Models\Activity;
 use BookStack\Activity\Models\Loggable;
 use BookStack\Activity\Notifications\Messages\PageCreationNotification;
 use BookStack\Activity\Tools\EntityWatchers;
@@ -11,13 +12,13 @@ use BookStack\Users\Models\User;
 
 class PageCreationNotificationHandler extends BaseNotificationHandler
 {
-    public function handle(string $activityType, Loggable|string $detail, User $user): void
+    public function handle(Activity $activity, Loggable|string $detail, User $user): void
     {
         if (!($detail instanceof Page)) {
             throw new \InvalidArgumentException("Detail for page create notifications must be a page");
         }
 
         $watchers = new EntityWatchers($detail, WatchLevels::NEW);
-        $this->sendNotificationToUserIds(PageCreationNotification::class, $watchers->getWatcherUserIds(), $user, $detail);
+        $this->sendNotificationToUserIds(PageCreationNotification::class, $watchers->getWatcherUserIds(), $user, $detail, $detail);
     }
 }
diff --git a/app/Activity/Notifications/Handlers/PageUpdateNotificationHandler.php b/app/Activity/Notifications/Handlers/PageUpdateNotificationHandler.php
index 5a2bf4e9c..744aba18f 100644
--- a/app/Activity/Notifications/Handlers/PageUpdateNotificationHandler.php
+++ b/app/Activity/Notifications/Handlers/PageUpdateNotificationHandler.php
@@ -2,6 +2,8 @@
 
 namespace BookStack\Activity\Notifications\Handlers;
 
+use BookStack\Activity\ActivityType;
+use BookStack\Activity\Models\Activity;
 use BookStack\Activity\Models\Loggable;
 use BookStack\Activity\Notifications\Messages\PageUpdateNotification;
 use BookStack\Activity\Tools\EntityWatchers;
@@ -12,15 +14,31 @@ use BookStack\Users\Models\User;
 
 class PageUpdateNotificationHandler extends BaseNotificationHandler
 {
-    public function handle(string $activityType, Loggable|string $detail, User $user): void
+    public function handle(Activity $activity, Loggable|string $detail, User $user): void
     {
         if (!($detail instanceof Page)) {
             throw new \InvalidArgumentException("Detail for page update notifications must be a page");
         }
 
+        // Get last update from activity
+        $lastUpdate = $detail->activity()
+            ->where('type', '=', ActivityType::PAGE_UPDATE)
+            ->where('id', '!=', $activity->id)
+            ->latest('created_at')
+            ->first();
+
+        // Return if the same user has already updated the page in the last 15 mins
+        if ($lastUpdate && $lastUpdate->user_id === $user->id) {
+            if ($lastUpdate->created_at->gt(now()->subMinutes(15))) {
+                return;
+            }
+        }
+
+        // Get active watchers
         $watchers = new EntityWatchers($detail, WatchLevels::UPDATES);
         $watcherIds = $watchers->getWatcherUserIds();
 
+        // Add page owner if preferences allow
         if (!$watchers->isUserIgnoring($detail->owned_by) && $detail->ownedBy) {
             $userNotificationPrefs = new UserNotificationPreferences($detail->ownedBy);
             if ($userNotificationPrefs->notifyOnOwnPageChanges()) {
@@ -28,6 +46,6 @@ class PageUpdateNotificationHandler extends BaseNotificationHandler
             }
         }
 
-        $this->sendNotificationToUserIds(PageUpdateNotification::class, $watcherIds, $user, $detail);
+        $this->sendNotificationToUserIds(PageUpdateNotification::class, $watcherIds, $user, $detail, $detail);
     }
 }
diff --git a/app/Activity/Notifications/LinkedMailMessageLine.php b/app/Activity/Notifications/MessageParts/LinkedMailMessageLine.php
similarity index 91%
rename from app/Activity/Notifications/LinkedMailMessageLine.php
rename to app/Activity/Notifications/MessageParts/LinkedMailMessageLine.php
index 224d8e87c..8f6a4e2b9 100644
--- a/app/Activity/Notifications/LinkedMailMessageLine.php
+++ b/app/Activity/Notifications/MessageParts/LinkedMailMessageLine.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace BookStack\Activity\Notifications;
+namespace BookStack\Activity\Notifications\MessageParts;
 
 use Illuminate\Contracts\Support\Htmlable;
 
diff --git a/app/Activity/Notifications/MessageParts/ListMessageLine.php b/app/Activity/Notifications/MessageParts/ListMessageLine.php
new file mode 100644
index 000000000..f808d2561
--- /dev/null
+++ b/app/Activity/Notifications/MessageParts/ListMessageLine.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace BookStack\Activity\Notifications\MessageParts;
+
+use Illuminate\Contracts\Support\Htmlable;
+
+/**
+ * A bullet point list of content, where the keys of the given list array
+ * are bolded header elements, and the values follow.
+ */
+class ListMessageLine implements Htmlable
+{
+    public function __construct(
+        protected array $list
+    ) {
+    }
+
+    public function toHtml(): string
+    {
+        $list = [];
+        foreach ($this->list as $header => $content) {
+            $list[] = '<strong>' . e($header) . '</strong> ' . e($content);
+        }
+        return implode("<br>\n", $list);
+    }
+}
diff --git a/app/Activity/Notifications/Messages/BaseActivityNotification.php b/app/Activity/Notifications/Messages/BaseActivityNotification.php
index 285e2803e..eb6eb0cc8 100644
--- a/app/Activity/Notifications/Messages/BaseActivityNotification.php
+++ b/app/Activity/Notifications/Messages/BaseActivityNotification.php
@@ -3,6 +3,7 @@
 namespace BookStack\Activity\Notifications\Messages;
 
 use BookStack\Activity\Models\Loggable;
+use BookStack\Activity\Notifications\MessageParts\LinkedMailMessageLine;
 use BookStack\Users\Models\User;
 use Illuminate\Bus\Queueable;
 use Illuminate\Notifications\Messages\MailMessage;
@@ -47,4 +48,16 @@ abstract class BaseActivityNotification extends Notification
             'activity_creator' => $this->user,
         ];
     }
+
+    /**
+     * Build the common reason footer line used in mail messages.
+     */
+    protected function buildReasonFooterLine(): LinkedMailMessageLine
+    {
+        return new LinkedMailMessageLine(
+            url('/preferences/notifications'),
+            trans('notifications.footer_reason'),
+            trans('notifications.footer_reason_link'),
+        );
+    }
 }
diff --git a/app/Activity/Notifications/Messages/CommentCreationNotification.php b/app/Activity/Notifications/Messages/CommentCreationNotification.php
index 817eb7b84..ce358724b 100644
--- a/app/Activity/Notifications/Messages/CommentCreationNotification.php
+++ b/app/Activity/Notifications/Messages/CommentCreationNotification.php
@@ -3,7 +3,7 @@
 namespace BookStack\Activity\Notifications\Messages;
 
 use BookStack\Activity\Models\Comment;
-use BookStack\Activity\Notifications\LinkedMailMessageLine;
+use BookStack\Activity\Notifications\MessageParts\ListMessageLine;
 use BookStack\Entities\Models\Page;
 use Illuminate\Notifications\Messages\MailMessage;
 
@@ -17,16 +17,14 @@ class CommentCreationNotification extends BaseActivityNotification
         $page = $comment->entity;
 
         return (new MailMessage())
-            ->subject("New Comment on Page: " . $page->getShortName())
-            ->line("A user has commented on a page in " . setting('app-name') . ':')
-            ->line("Page Name: " . $page->name)
-            ->line("Commenter: " . $this->user->name)
-            ->line("Comment: " . strip_tags($comment->html))
-            ->action('View Comment', $page->getUrl('#comment' . $comment->local_id))
-            ->line(new LinkedMailMessageLine(
-                url('/preferences/notifications'),
-                'This notification was sent to you because :link cover this type of activity for this item.',
-                'your notification preferences',
-            ));
+            ->subject(trans('notifications.new_comment_subject', ['pageName' => $page->getShortName()]))
+            ->line(trans('notifications.new_comment_intro', ['appName' => setting('app-name')]))
+            ->line(new ListMessageLine([
+                trans('notifications.detail_page_name') => $page->name,
+                trans('notifications.detail_commenter') => $this->user->name,
+                trans('notifications.detail_comment') => strip_tags($comment->html),
+            ]))
+            ->action(trans('notifications.action_view_comment'), $page->getUrl('#comment' . $comment->local_id))
+            ->line($this->buildReasonFooterLine());
     }
 }
diff --git a/app/Activity/Notifications/Messages/PageCreationNotification.php b/app/Activity/Notifications/Messages/PageCreationNotification.php
index 2e9a6debc..068f95acc 100644
--- a/app/Activity/Notifications/Messages/PageCreationNotification.php
+++ b/app/Activity/Notifications/Messages/PageCreationNotification.php
@@ -2,7 +2,7 @@
 
 namespace BookStack\Activity\Notifications\Messages;
 
-use BookStack\Activity\Notifications\LinkedMailMessageLine;
+use BookStack\Activity\Notifications\MessageParts\ListMessageLine;
 use BookStack\Entities\Models\Page;
 use Illuminate\Notifications\Messages\MailMessage;
 
@@ -14,15 +14,13 @@ class PageCreationNotification extends BaseActivityNotification
         $page = $this->detail;
 
         return (new MailMessage())
-            ->subject("New Page: " . $page->getShortName())
-            ->line("A new page has been created in " . setting('app-name') . ':')
-            ->line("Page Name: " . $page->name)
-            ->line("Created By: " . $this->user->name)
-            ->action('View Page', $page->getUrl())
-            ->line(new LinkedMailMessageLine(
-                url('/preferences/notifications'),
-                'This notification was sent to you because :link cover this type of activity for this item.',
-                'your notification preferences',
-            ));
+            ->subject(trans('notifications.new_page_subject', ['pageName' => $page->getShortName()]))
+            ->line(trans('notifications.new_page_intro', ['appName' => setting('app-name')]))
+            ->line(new ListMessageLine([
+                trans('notifications.detail_page_name') => $page->name,
+                trans('notifications.detail_created_by') => $this->user->name,
+            ]))
+            ->action(trans('notifications.action_view_page'), $page->getUrl())
+            ->line($this->buildReasonFooterLine());
     }
 }
diff --git a/app/Activity/Notifications/Messages/PageUpdateNotification.php b/app/Activity/Notifications/Messages/PageUpdateNotification.php
index f29f50dde..c4a6de0bd 100644
--- a/app/Activity/Notifications/Messages/PageUpdateNotification.php
+++ b/app/Activity/Notifications/Messages/PageUpdateNotification.php
@@ -2,7 +2,7 @@
 
 namespace BookStack\Activity\Notifications\Messages;
 
-use BookStack\Activity\Notifications\LinkedMailMessageLine;
+use BookStack\Activity\Notifications\MessageParts\ListMessageLine;
 use BookStack\Entities\Models\Page;
 use Illuminate\Notifications\Messages\MailMessage;
 
@@ -14,16 +14,14 @@ class PageUpdateNotification extends BaseActivityNotification
         $page = $this->detail;
 
         return (new MailMessage())
-            ->subject("Updated Page: " . $page->getShortName())
-            ->line("A page has been updated in " . setting('app-name') . ':')
-            ->line("Page Name: " . $page->name)
-            ->line("Updated By: " . $this->user->name)
-            ->line("To prevent a mass of notifications, for a while you won't be sent notifications for further edits to this page by the same editor.")
-            ->action('View Page', $page->getUrl())
-            ->line(new LinkedMailMessageLine(
-                url('/preferences/notifications'),
-                'This notification was sent to you because :link cover this type of activity for this item.',
-                'your notification preferences',
-            ));
+            ->subject(trans('notifications.updated_page_subject', ['pageName' => $page->getShortName()]))
+            ->line(trans('notifications.updated_page_intro', ['appName' => setting('app-name')]))
+            ->line(new ListMessageLine([
+                trans('notifications.detail_page_name') => $page->name,
+                trans('notifications.detail_updated_by') => $this->user->name,
+            ]))
+            ->line(trans('notifications.updated_page_debounce'))
+            ->action(trans('notifications.action_view_page'), $page->getUrl())
+            ->line($this->buildReasonFooterLine());
     }
 }
diff --git a/app/Activity/Notifications/NotificationManager.php b/app/Activity/Notifications/NotificationManager.php
index 01361c1ee..fc6a5f57c 100644
--- a/app/Activity/Notifications/NotificationManager.php
+++ b/app/Activity/Notifications/NotificationManager.php
@@ -3,6 +3,7 @@
 namespace BookStack\Activity\Notifications;
 
 use BookStack\Activity\ActivityType;
+use BookStack\Activity\Models\Activity;
 use BookStack\Activity\Models\Loggable;
 use BookStack\Activity\Notifications\Handlers\CommentCreationNotificationHandler;
 use BookStack\Activity\Notifications\Handlers\NotificationHandler;
@@ -17,13 +18,14 @@ class NotificationManager
      */
     protected array $handlers = [];
 
-    public function handle(string $activityType, string|Loggable $detail, User $user): void
+    public function handle(Activity $activity, string|Loggable $detail, User $user): void
     {
+        $activityType = $activity->type;
         $handlersToRun = $this->handlers[$activityType] ?? [];
         foreach ($handlersToRun as $handlerClass) {
             /** @var NotificationHandler $handler */
             $handler = app()->make($handlerClass);
-            $handler->handle($activityType, $detail, $user);
+            $handler->handle($activity, $detail, $user);
         }
     }
 
diff --git a/app/Activity/Tools/ActivityLogger.php b/app/Activity/Tools/ActivityLogger.php
index e8ea7c293..adda36c1b 100644
--- a/app/Activity/Tools/ActivityLogger.php
+++ b/app/Activity/Tools/ActivityLogger.php
@@ -40,7 +40,7 @@ class ActivityLogger
 
         $this->setNotification($type);
         $this->dispatchWebhooks($type, $detail);
-        $this->notifications->handle($type, $detail, user());
+        $this->notifications->handle($activity, $detail, user());
         Theme::dispatch(ThemeEvents::ACTIVITY_LOGGED, $type, $detail);
     }
 
diff --git a/app/Activity/Tools/UserEntityWatchOptions.php b/app/Activity/Tools/UserEntityWatchOptions.php
index 26d830851..08fbe2e8d 100644
--- a/app/Activity/Tools/UserEntityWatchOptions.php
+++ b/app/Activity/Tools/UserEntityWatchOptions.php
@@ -76,14 +76,16 @@ class UserEntityWatchOptions
             $entities[] = $this->entity->chapter;
         }
 
-        $query = Watch::query()->where(function (Builder $subQuery) use ($entities) {
-            foreach ($entities as $entity) {
-                $subQuery->orWhere(function (Builder $whereQuery) use ($entity) {
-                    $whereQuery->where('watchable_type', '=', $entity->getMorphClass())
+        $query = Watch::query()
+            ->where('user_id', '=', $this->user->id)
+            ->where(function (Builder $subQuery) use ($entities) {
+                foreach ($entities as $entity) {
+                    $subQuery->orWhere(function (Builder $whereQuery) use ($entity) {
+                        $whereQuery->where('watchable_type', '=', $entity->getMorphClass())
                         ->where('watchable_id', '=', $entity->id);
-                });
-            }
-        });
+                    });
+                }
+            });
 
         $this->watchMap = $query->get(['watchable_type', 'level'])
             ->pluck('level', 'watchable_type')
diff --git a/lang/en/notifications.php b/lang/en/notifications.php
new file mode 100644
index 000000000..5539ae9a9
--- /dev/null
+++ b/lang/en/notifications.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * Text used for activity-based notifications.
+ */
+return [
+
+    'new_comment_subject' => 'New comment on page: :pageName',
+    'new_comment_intro' => 'A user has commented on a page in :appName:',
+    'new_page_subject' => 'New page: :pageName',
+    'new_page_intro' => 'A new page has been created in :appName:',
+    'updated_page_subject' => 'Updated page: :pageName',
+    'updated_page_intro' => 'A page has been updated in :appName:',
+    'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.',
+
+    'detail_page_name' => 'Page Name:',
+    'detail_commenter' => 'Commenter:',
+    'detail_comment' => 'Comment:',
+    'detail_created_by' => 'Created By:',
+    'detail_updated_by' => 'Updated By:',
+
+    'action_view_comment' => 'View Comment',
+    'action_view_page' => 'View Page',
+
+    'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.',
+    'footer_reason_link' => 'your notification preferences',
+];
diff --git a/resources/views/vendor/notifications/email.blade.php b/resources/views/vendor/notifications/email.blade.php
index f73b87b59..88cdbd890 100644
--- a/resources/views/vendor/notifications/email.blade.php
+++ b/resources/views/vendor/notifications/email.blade.php
@@ -30,7 +30,7 @@
 $style = [
     /* Layout ------------------------------ */
 
-    'body' => 'margin: 0; padding: 0; width: 100%; background-color: #F2F4F6;',
+    'body' => 'margin: 0; padding: 0; width: 100%; background-color: #F2F4F6;color:#444444;',
     'email-wrapper' => 'width: 100%; margin: 0; padding: 0; background-color: #F2F4F6;',
 
     /* Masthead ----------------------- */
@@ -54,8 +54,8 @@ $style = [
 
     'anchor' => 'color: '.setting('app-color').';overflow-wrap: break-word;word-wrap: break-word;word-break: break-all;word-break:break-word;',
     'header-1' => 'margin-top: 0; color: #2F3133; font-size: 19px; font-weight: bold; text-align: left;',
-    'paragraph' => 'margin-top: 0; color: #74787E; font-size: 16px; line-height: 1.5em;',
-    'paragraph-sub' => 'margin-top: 0; color: #74787E; font-size: 12px; line-height: 1.5em;',
+    'paragraph' => 'margin-top: 0; color: #444444; font-size: 16px; line-height: 1.5em;',
+    'paragraph-sub' => 'margin-top: 0; color: #444444; font-size: 12px; line-height: 1.5em;',
     'paragraph-center' => 'text-align: center;',
 
     /* Buttons ------------------------------ */
@@ -147,7 +147,7 @@ $style = [
 
                                                     <!-- Outro -->
                                                     @foreach ($outroLines as $line)
-                                                        <p style="{{ $style['paragraph'] }}">
+                                                        <p style="{{ $style['paragraph-sub'] }}">
                                                             {{ $line }}
                                                         </p>
                                                     @endforeach