Skip to main content
Back to ScopeForged

ScopeForged Documentation

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

Communication & Real-time/Notification Hub

Notification Hub Feature Guide

Overview

The Notification Hub is a comprehensive multi-channel notification system that supports email, SMS, Slack, and in-app notifications. It provides users with granular control over their notification preferences, quiet hours, and channel configurations.

Key Features

  • Multi-channel delivery: Email, SMS (Twilio), Slack webhooks, and in-app notifications
  • User preferences: Per-notification-type, per-channel preferences
  • Quiet hours: Configurable do-not-disturb periods with timezone support
  • Digest batching: Hourly, daily, or weekly notification digests
  • One-click unsubscribe: Token-based email unsubscribe links
  • Template system: Customizable notification templates with variable substitution
  • Admin management: Full CRUD for notification types and templates

Architecture

Models

ModelDescription
NotificationChannelAvailable notification channels (email, SMS, etc.)
NotificationTypeNotification types with categories (billing, project, security)
NotificationPreferenceUser preferences per type/channel
UserQuietHoursUser-configurable quiet hours
UserChannelConfigChannel-specific configuration (phone number, webhook URL)
NotificationQueuePending notifications awaiting delivery
NotificationDeliveryDelivery tracking and history
NotificationDigestBatched notification digests
NotificationTemplateCustomizable message templates
UnsubscribeTokenToken-based unsubscribe links

Services

  • NotificationHub: Central service for sending and managing notifications
  • DigestService: Processes and sends notification digests

Channel Drivers

Located in App\Services\Notifications\Channels\:

  • EmailChannel - Sends via Laravel Mail
  • InAppChannel - Database notifications
  • SlackChannel - Slack webhook integration
  • SmsChannel - Twilio SMS integration

Usage

Sending Notifications

use App\Services\Notifications\NotificationHub;

// Inject or resolve the hub
$hub = app(NotificationHub::class);

// Send a notification
$hub->send($user, 'invoice.created', [
    'user_name' => $user->name,
    'invoice_number' => 'INV-001',
    'amount' => '$1,500.00',
    'due_date' => 'January 15, 2024',
]);

Processing the Queue

Run via scheduler or manually:

use App\Jobs\ProcessNotificationQueueJob;

// Process pending notifications
ProcessNotificationQueueJob::dispatch();

Sending Digests

use App\Jobs\SendNotificationDigestsJob;

// Send all digest frequencies
SendNotificationDigestsJob::dispatchAll();

// Or specific frequency
SendNotificationDigestsJob::dispatch('daily');
use App\Models\UnsubscribeToken;

// Generate for all notifications
$token = UnsubscribeToken::generateForUser($user, 'all');

// Generate for a category
$token = UnsubscribeToken::generateForUser($user, 'category:billing');

// Generate for a specific type
$token = UnsubscribeToken::generateForUser($user, 'notification_type:invoice.created');

$unsubscribeUrl = route('notifications.unsubscribe.show', $token->token);

Routes

User Routes (requires auth)

MethodURINameDescription
GET/settings/notificationssettings.notifications.indexView preferences
PUT/settings/notifications/preferencessettings.notifications.preferencesUpdate preferences
PUT/settings/notifications/quiet-hourssettings.notifications.quiet-hoursUpdate quiet hours
POST/settings/notifications/channels/{channel}/setupsettings.notifications.channel.setupConfigure channel
POST/settings/notifications/channels/{channel}/verifysettings.notifications.channel.verifyVerify channel
DELETE/settings/notifications/channels/{channel}settings.notifications.channel.removeRemove channel
POST/settings/notifications/resubscribesettings.notifications.resubscribeRe-enable all

Public Routes

MethodURINameDescription
GET/unsubscribe/{token}notifications.unsubscribe.showView unsubscribe page
POST/unsubscribe/{token}notifications.unsubscribe.processProcess unsubscribe

Admin Routes (requires admin)

MethodURINameDescription
GET/admin/notifications/typesadmin.notifications.types.indexList types
POST/admin/notifications/typesadmin.notifications.types.storeCreate type
PUT/admin/notifications/types/{type}admin.notifications.types.updateUpdate type
DELETE/admin/notifications/types/{type}admin.notifications.types.destroyDelete type
GET/admin/notifications/templatesadmin.notifications.templates.indexList templates
POST/admin/notifications/templatesadmin.notifications.templates.storeCreate template
PUT/admin/notifications/templates/{template}admin.notifications.templates.updateUpdate template
DELETE/admin/notifications/templates/{template}admin.notifications.templates.destroyDelete template
GET/admin/notifications/templates/{template}/previewadmin.notifications.templates.previewPreview template
POST/admin/notifications/templates/{template}/duplicateadmin.notifications.templates.duplicateDuplicate template

Notification Types

Default notification types are seeded via NotificationHubSeeder:

Billing

  • invoice.created - When a new invoice is created
  • invoice.sent - When an invoice is sent
  • invoice.paid - When payment is received
  • invoice.overdue - When an invoice becomes overdue (high priority)

Project

  • project.created - When a new project is created
  • project.status_changed - When project status changes
  • project.file_uploaded - When a file is uploaded (low priority)
  • message.received - When a new message is received

Security (cannot unsubscribe)

  • security.login - New login detected
  • security.password_changed - Password was changed (urgent)

System

  • system.maintenance - Scheduled maintenance notifications

Template Variables

Templates support Blade-style variable substitution:

<p>Hello {{ user_name }},</p>
<p>A new invoice #{{ invoice_number }} for {{ amount }} has been created.</p>
<p>Due date: {{ due_date }}</p>

Configuration

Environment Variables

# Twilio (for SMS)
TWILIO_SID=your_account_sid
TWILIO_AUTH_TOKEN=your_auth_token
TWILIO_PHONE_NUMBER=+15551234567

# Slack (webhook URLs configured per-user)

Scheduler (Console Kernel)

Add to app/Console/Kernel.php:

protected function schedule(Schedule $schedule): void
{
    // Process notification queue every minute
    $schedule->job(new ProcessNotificationQueueJob)->everyMinute();

    // Send hourly digests
    $schedule->job(new SendNotificationDigestsJob('hourly'))->hourly();

    // Send daily digests at 8am
    $schedule->job(new SendNotificationDigestsJob('daily'))->dailyAt('08:00');

    // Send weekly digests on Monday at 8am
    $schedule->job(new SendNotificationDigestsJob('weekly'))->weeklyOn(1, '08:00');
}

Quiet Hours

Users can configure quiet hours to suppress notifications during specific times:

  • Start/End Time: 24-hour format (e.g., 22:00 - 08:00)
  • Days of Week: 0 (Sunday) through 6 (Saturday)
  • Timezone: User's local timezone
  • Allow Urgent: Optional bypass for urgent notifications

When quiet hours are active, notifications are queued and delivered after quiet hours end.

Channel Verification

Some channels (like SMS) require verification:

  1. User submits phone number via channel setup
  2. System generates 6-digit verification code
  3. Code is sent to the phone number
  4. User submits code via channel verify endpoint
  5. Channel is marked as verified and enabled

Security Considerations

  • Security-category notifications cannot be unsubscribed (e.g., login alerts)
  • Unsubscribe tokens expire after 7 days
  • Channel verification codes expire after 10 minutes
  • Webhook URLs are validated before saving

Testing

Run notification hub tests:

php artisan test tests/Feature/NotificationHubTest.php

Email Tracking with Resend

The notification system supports email tracking (opens, clicks, bounces) via Resend webhooks.

Setup

  1. Enable tracking in Resend Dashboard:

    • Go to Resend Dashboard
    • Select your domain → Configuration
    • Enable Open Tracking and Click Tracking
  2. Create webhook in Resend:

    • Go to Webhooks → Add Webhook
    • URL: https://yourdomain.com/webhooks/resend
    • Select events: email.sent, email.delivered, email.opened, email.clicked, email.bounced, email.complained, email.delivery_delayed, email.failed, email.suppressed
    • Save and copy the signing secret
  3. Configure environment:

    MAIL_MAILER=resend
    RESEND_API_KEY=re_your_api_key
    RESEND_WEBHOOK_SECRET=whsec_your_signing_secret
    

Tracked Events

EventDescriptionUpdates
email.sentEmail queued for delivery-
email.deliveredEmail delivered to inboxstatus, delivered_at
email.openedRecipient opened emailopened_at, open count
email.clickedRecipient clicked a linkclicked_at, clicked URL
email.bouncedEmail bounced (hard/soft)status, bounce type
email.complainedRecipient marked as spamstatus
email.delivery_delayedDelivery is delayedstatus, delay reason
email.failedDelivery permanently failedstatus, failure reason
email.suppressedEmail suppressed (previous bounce)status

Delivery Statuses

NotificationDelivery::STATUS_SENT       // 'sent'
NotificationDelivery::STATUS_DELIVERED  // 'delivered'
NotificationDelivery::STATUS_BOUNCED    // 'bounced'
NotificationDelivery::STATUS_FAILED     // 'failed'
NotificationDelivery::STATUS_COMPLAINED // 'complained'
NotificationDelivery::STATUS_DELAYED    // 'delayed'
NotificationDelivery::STATUS_SUPPRESSED // 'suppressed'

Tracking Data

The tracking_data JSON column stores additional webhook data:

// Example tracking_data for opened + clicked email
[
    'opens' => 3,
    'opens_last' => '2024-01-22T10:30:00Z',
    'clicks' => 1,
    'last_click' => '2024-01-22T10:31:00Z',
    'clicked_url' => 'https://example.com/invoice/123',
    'last_user_agent' => 'Mozilla/5.0...',
]

Webhook Controller

Located at App\Http\Controllers\Webhooks\ResendWebhookController:

  • Verifies Svix webhook signatures
  • Matches emails by external_id (Resend email ID)
  • Updates delivery status and tracking data
  • App\Services\Notifications\NotificationHub - Core service
  • App\Services\Notifications\DigestService - Digest processing
  • App\Services\Notifications\Channels\EmailChannel - Email delivery with Resend
  • App\Http\Controllers\Webhooks\ResendWebhookController - Webhook handler
  • App\Providers\NotificationHubServiceProvider - Service registration
  • App\Http\Controllers\NotificationHubController - User preferences
  • App\Http\Controllers\Admin\NotificationTypeController - Admin types
  • App\Http\Controllers\Admin\NotificationTemplateController - Admin templates
  • database/seeders/NotificationHubSeeder.php - Default data