Skip to main content
Back to ScopeForged

ScopeForged Documentation

Technical documentation, guides, and feature references for the ScopeForged client portal.

Communication & Real-time/Notifications

Notifications Guide

Last Updated: 2026-01-13 Status: Implemented Plan Reference: 010-notifications-email.md, 079-notifications-improvement.md


Overview

The Notifications system handles both email and in-app notifications to keep users informed about important events. It supports multiple channels (mail, database), notification preferences, and integrates with Laravel's notification system.


Table of Contents

  1. Accessing Notifications
  2. Features
  3. Notification Center
  4. Notification Digest
  5. Notification Templates
  6. Notification Types
  7. How to Use
  8. Email Configuration
  9. Technical Architecture
  10. Delivery Tracking Dashboard
  11. Related Features

Accessing Notifications

Access PointLocationURLRole
Notification BellTop navigationClick bell iconBoth
All NotificationsDropdown menu/notificationsBoth
Notification PreferencesProfile settings/profile#notificationsBoth

Permissions

ActionAdminClient User
View own notifications
Mark as read
Delete notifications
Configure preferences

Features

In-App Notifications

  • Real-time notification bell
  • Unread count badge
  • Notification dropdown
  • Mark as read/unread
  • Delete notifications

Email Notifications

  • HTML email templates
  • Plain text fallback
  • Customizable sender
  • Queue for performance
  • Retry on failure

Notification Preferences

  • Per-notification-type settings
  • Email on/off toggle
  • In-app on/off toggle
  • Frequency settings

Notification Center

The notification center (/notifications) provides comprehensive notification management with advanced filtering.

Features

  • Filtering: View all, unread only, or read only notifications
  • Type Filtering: Filter by notification type
  • Mark as Read: Individual or bulk mark as read
  • Delete: Remove individual notifications
  • Clear All: Delete all notifications at once
  • Pagination: 20 notifications per page

AJAX Endpoints

EndpointMethodPurpose
/notifications/recentGETGet recent notifications (JSON)
/notifications/unread-countGETGet unread count (JSON)
/notifications/{id}/readPOSTMark single notification as read
/notifications/mark-all-readPOSTMark all as read
/notifications/clear-allDELETEDelete all notifications

JavaScript Integration

// Fetch unread count
fetch('/notifications/unread-count')
    .then(res => res.json())
    .then(data => updateBadge(data.count));

// Fetch recent notifications
fetch('/notifications/recent')
    .then(res => res.json())
    .then(data => renderNotifications(data.notifications));

// Mark as read (AJAX)
fetch(`/notifications/${id}/read`, { method: 'POST' })
    .then(res => res.json())
    .then(data => updateUI());

Notification Digest

The digest system sends consolidated notification summaries to users who prefer batched emails.

User Preferences

Users can enable digest mode in their profile settings:

PreferenceColumnDefault
Enable Digestnotification_digestfalse
Push Notificationspush_notifications_enabledfalse
Email Notificationsemail_notifications_enabledtrue
Digest Frequencydigest_frequencydaily

Digest Service

Location: app/Services/NotificationDigestService.php

use App\Services\NotificationDigestService;

$service = new NotificationDigestService();

// Send digests to all eligible users
$count = $service->sendDigests();

// Check if user is eligible
$eligible = $service->isEligibleForDigest($user);

// Preview digest for a specific user
$preview = $service->getDigestPreview($user);

Scheduling Digests

Add to scheduler (app/Console/Kernel.php):

$schedule->call(function () {
    app(NotificationDigestService::class)->sendDigests();
})->dailyAt('08:00');

Digest Email Contents

  • Groups notifications by type
  • Shows count per type
  • Lists individual notification messages
  • Links to full notification center

Notification Templates

Templates allow customization of notification content with variable substitution.

Template Model

Location: app/Models/NotificationTemplate.php

ColumnTypeDescription
namestringDisplay name
typestringUnique type identifier
subjectstringEmail subject with variables
bodytextEmail body with variables
channelsjsonDelivery channels
variablesjsonAvailable variables
is_activebooleanTemplate status

Creating Templates

use App\Models\NotificationTemplate;

$template = NotificationTemplate::create([
    'name' => 'Project Created',
    'type' => 'project_created',
    'subject' => 'New Project: {{ project_name }}',
    'body' => 'A new project "{{ project_name }}" has been created for {{ client_name }}.',
    'channels' => ['mail', 'database'],
    'variables' => ['project_name', 'client_name'],
    'is_active' => true,
]);

Rendering Templates

$template = NotificationTemplate::findByType('project_created');

$rendered = $template->render([
    'project_name' => 'Website Redesign',
    'client_name' => 'Acme Corp',
]);

// Result:
// [
//     'subject' => 'New Project: Website Redesign',
//     'body' => 'A new project "Website Redesign" has been created for Acme Corp.',
// ]

Template Scopes

// Find active template by type
$template = NotificationTemplate::findByType('invoice_created');

// Get all active templates
$active = NotificationTemplate::active()->get();

// Filter by type
$invoiceTemplates = NotificationTemplate::ofType('invoice%')->get();

Variable Placeholders

Templates use {{ variable }} syntax for placeholders:

// Get available placeholders
$placeholders = $template->getPlaceholders();
// Returns: ['project_name', 'client_name']

Notification Types

Admin Notifications

NotificationTriggerChannels
New Client RegistrationClient user registersEmail, Database
Invoice OverdueInvoice past due dateEmail, Database
Project Status ChangeProject status updatedDatabase
File UploadedClient uploads fileEmail, Database
New Support MessageClient sends messageEmail, Database

Client Notifications

NotificationTriggerChannels
Invoice CreatedNew invoice createdEmail, Database
Invoice ReminderApproaching due dateEmail
Project UpdateProject status changedEmail, Database
File SharedNew file made visibleEmail, Database
Welcome EmailAccount createdEmail

How to Use

Viewing Notifications

  1. Click the bell icon in the navigation bar
  2. See recent unread notifications
  3. Click "View All" for full list
  4. Click a notification to see details

Managing Notifications

Mark as Read:

  1. Click notification in dropdown
  2. Or click "Mark as Read" button
  3. Or click "Mark All as Read"

Delete Notification:

  1. Navigate to notifications page
  2. Click delete icon next to notification
  3. Confirm deletion

Configuring Preferences

  1. Navigate to Profile → Notification Preferences
  2. Toggle settings for each notification type:
    • Email: Receive email notifications
    • In-App: Show in notification center
  3. Click "Save Preferences"

Email Configuration

Environment Variables

MAIL_MAILER=smtp
MAIL_HOST=smtp.mailgun.org
MAIL_PORT=587
MAIL_USERNAME=your-username
MAIL_PASSWORD=your-password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=noreply@yourcompany.com
MAIL_FROM_NAME="Your Company"

Supported Mail Drivers

DriverUse Case
smtpStandard SMTP server
mailgunMailgun API
sesAmazon SES
postmarkPostmark API
logLocal development (logs only)

Email Templates

Email templates are located in resources/views/emails/:

TemplatePurpose
invoice-created.blade.phpNew invoice notification
invoice-reminder.blade.phpPayment reminder
welcome.blade.phpWelcome email
file-shared.blade.phpFile notification
project-update.blade.phpProject status change

Technical Architecture

Notification Classes

Location: app/Notifications/

// Example: InvoiceCreated notification
class InvoiceCreated extends Notification implements ShouldQueue
{
    use Queueable;

    public function __construct(
        public Invoice $invoice
    ) {}

    public function via(object $notifiable): array
    {
        return ['mail', 'database'];
    }

    public function toMail(object $notifiable): MailMessage
    {
        return (new MailMessage)
            ->subject('New Invoice #' . $this->invoice->invoice_number)
            ->line('A new invoice has been created.')
            ->action('View Invoice', route('portal.invoices.show', $this->invoice))
            ->line('Amount due: $' . number_format($this->invoice->total, 2));
    }

    public function toArray(object $notifiable): array
    {
        return [
            'type' => 'invoice_created',
            'invoice_id' => $this->invoice->id,
            'invoice_number' => $this->invoice->invoice_number,
            'amount' => $this->invoice->total,
            'message' => 'New invoice #' . $this->invoice->invoice_number,
        ];
    }
}

Sending Notifications

// Send to single user
$user->notify(new InvoiceCreated($invoice));

// Send to multiple users
Notification::send($users, new InvoiceCreated($invoice));

// Queue notification
$user->notify((new InvoiceCreated($invoice))->delay(now()->addMinutes(5)));

Available Notifications

ClassEvent
InvoiceCreatedInvoice created
InvoiceReminderInvoice due soon
InvoiceOverdueInvoice past due
ProjectStatusChangedProject status updated
FileSharedFile made visible
WelcomeNotificationUser registered
DocumentRequestCreatedDocument requested
DeliverableSubmittedDeliverable ready

Controller

Location: app/Http/Controllers/NotificationController.php

MethodRouteDescription
index()GET /notificationsList notifications with filtering
recent()GET /notifications/recentRecent notifications (JSON)
unreadCount()GET /notifications/unread-countUnread count (JSON)
markAsRead()POST /notifications/{id}/readMark one read
markAllAsRead()POST /notifications/mark-all-readMark all read
clearAll()DELETE /notifications/clear-allDelete all notifications
destroy()DELETE /notifications/{id}Delete one

Routes

Route::middleware('auth')->prefix('notifications')->name('notifications.')->group(function () {
    Route::get('/', [NotificationController::class, 'index'])->name('index');
    Route::get('/recent', [NotificationController::class, 'recent'])->name('recent');
    Route::get('/unread-count', [NotificationController::class, 'unreadCount'])->name('unread-count');
    Route::post('/{id}/read', [NotificationController::class, 'markAsRead'])->name('read');
    Route::post('/mark-all-read', [NotificationController::class, 'markAllAsRead'])->name('mark-all-read');
    Route::delete('/clear-all', [NotificationController::class, 'clearAll'])->name('clear-all');
    Route::delete('/{id}', [NotificationController::class, 'destroy'])->name('destroy');
});

Database Schema

Table: notifications (Laravel default)

ColumnTypeDescription
iduuidPrimary key
typestringNotification class name
notifiable_typestringUser model
notifiable_idbigintUser ID
datajsonNotification data
read_attimestampWhen read
created_attimestampWhen created

Table: notification_preferences

ColumnTypeDescription
idbigintPrimary key
user_idbigintUser FK
notification_typestringType identifier
email_enabledbooleanEmail toggle
database_enabledbooleanIn-app toggle

Table: notification_templates

ColumnTypeDescription
idbigintPrimary key
namestringTemplate display name
typestringUnique type identifier
subjectstringEmail subject with variables
bodytextEmail body with variables
channelsjsonDelivery channels
variablesjsonAvailable variables
is_activebooleanTemplate enabled
created_attimestampCreated timestamp
updated_attimestampUpdated timestamp

User Preference Columns (added to users table)

ColumnTypeDefaultDescription
notification_digestbooleanfalseEnable digest emails
push_notifications_enabledbooleanfalseEnable push notifications
email_notifications_enabledbooleantrueEnable email notifications
digest_frequencystringdailyDigest frequency

User Model Methods

// Get unread notifications
$user->unreadNotifications;

// Get all notifications
$user->notifications;

// Get unread count
$user->unreadNotifications()->count();

// Mark specific notification as read
$notification->markAsRead();

// Mark all as read
$user->unreadNotifications->markAsRead();

Notification Preferences

Default Preferences

// app/Models/User.php
public function getNotificationPreference(string $type): array
{
    $preference = $this->notificationPreferences()
        ->where('notification_type', $type)
        ->first();

    return [
        'email' => $preference?->email_enabled ?? true,
        'database' => $preference?->database_enabled ?? true,
    ];
}

Checking Preferences

// In notification class
public function via(object $notifiable): array
{
    $channels = [];
    $prefs = $notifiable->getNotificationPreference('invoice_created');

    if ($prefs['mail']) {
        $channels[] = 'mail';
    }
    if ($prefs['database']) {
        $channels[] = 'database';
    }

    return $channels;
}

Delivery Tracking Dashboard

The delivery dashboard provides real-time monitoring of notification deliveries with retry management for failed notifications.

Accessing the Dashboard

  • URL: Admin → Notifications → Delivery (/admin/notifications/delivery)
  • Navigation: Click "Delivery" in the notification management navigation bar

Dashboard Features

FeatureDescription
Summary CardsReal-time counts for sent, delivered, failed, opened, clicked
Delivery RatePercentage of successfully delivered notifications
Open/Click RatesEngagement metrics for delivered notifications
Queue StatusPending, scheduled, processing, sent, failed counts
Timeline ChartHourly delivery breakdown over last 24 hours
Channel BreakdownDelivery stats per channel (email, database, etc.)
Failed NotificationsList of failed notifications with retry actions

Retry Management

The system supports automatic and manual retry of failed notifications:

use App\Services\Notifications\NotificationDeliveryService;

$service = app(NotificationDeliveryService::class);

// Retry single notification
$result = $service->retryNotification($queueId);

// Bulk retry multiple notifications
$result = $service->bulkRetry([1, 2, 3]);

// Retry all eligible failed notifications
$result = $service->retryAllFailed();

Retry Delays

Failed notifications are retried with exponential backoff:

AttemptDelay
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry8 hours
Maximum5 retries total

Dashboard API Endpoints

EndpointMethodDescription
/admin/notifications/deliveryGETDashboard view
/admin/notifications/delivery/metricsGETJSON metrics for AJAX refresh
/admin/notifications/delivery/failedGETFailed notifications list
/admin/notifications/delivery/{id}/retryPOSTRetry single notification
/admin/notifications/delivery/bulk-retryPOSTRetry multiple notifications
/admin/notifications/delivery/retry-allPOSTRetry all eligible
/admin/notifications/delivery/{id}/cancelPOSTCancel queued notification
/admin/notifications/delivery/detail/{uuid}GETDelivery details
/admin/notifications/delivery/clear-oldPOSTClear old queue items

RetryNotificationJob

Failed notifications are retried via a queued job:

use App\Jobs\RetryNotificationJob;
use App\Models\NotificationQueue;

// Dispatch retry job with delay
$notification = NotificationQueue::find($id);
RetryNotificationJob::dispatch($notification)
    ->delay(now()->addMinutes(5));

Database Tables

Table: notification_queue

ColumnTypeDescription
idbigintPrimary key
uuiduuidUnique identifier
user_idbigintTarget user
notification_type_idbigintNotification type
channelstringDelivery channel
statusstringpending/scheduled/processing/sent/failed/cancelled
priorityintegerDelivery priority
attempt_countintegerNumber of attempts
last_errortextLast error message
sent_attimestampWhen successfully sent
created_attimestampQueue time

Table: notification_deliveries

ColumnTypeDescription
idbigintPrimary key
uuiduuidTracking identifier
user_idbigintRecipient user
notification_type_idbigintNotification type
channelstringDelivery channel
statusstringdelivered/failed
datajsonNotification content
external_idstringExternal service ID
delivered_attimestampDelivery timestamp
opened_attimestampWhen opened
clicked_attimestampWhen clicked
tracking_datajsonAdditional tracking

Dependencies

FeatureRelationship
AuthenticationNotifications require login
Background JobsQueued email sending

Complementary Features

FeatureDescription
WebhooksExternal notifications
RealtimePush notifications
Activity LoggingLogs notification events

Best Practices

For Administrators

  1. Configure email properly before going live
  2. Test email delivery with real addresses
  3. Monitor failed emails in logs
  4. Respect user preferences for notifications

For Developers

  1. Queue all email notifications for performance
  2. Keep notification data minimal in database
  3. Use meaningful notification types for preferences
  4. Implement ShouldQueue interface

Troubleshooting

IssueSolution
Emails not sendingCheck MAIL_* config and queue worker
Notification not showingVerify database channel in via()
Duplicate notificationsCheck if event fires multiple times
Slow notificationEnsure ShouldQueue is implemented

Testing Email Locally

# Use log driver for testing
MAIL_MAILER=log

# Check logs
tail -f storage/logs/laravel.log

Queue Issues

# Check queue
php artisan queue:work

# Check failed jobs
php artisan queue:failed

# Retry failed
php artisan queue:retry all

See Also