- Create notification management system with VNotifications class - Build notification API endpoints (get, mark read, delete, preferences) - Implement notification bell UI widget with real-time polling - Add email queue system with digest support (instant/hourly/daily/weekly) - Create notification trigger helpers for common events - Add fine-grained user preference controls - Implement cron script for email processing and cleanup - Document setup and usage in docs/NOTIFICATIONS.md Features: - In-app notifications with dropdown - Email delivery with batching - 6 notification types (comment, like, subscribe, upload, mention, system) - User preference controls per notification type - Lightweight polling (30s interval) - Auto-cleanup of old notifications (90 days) - Production-ready with proper error handling
9.7 KiB
Notification System
EasyStream now includes a comprehensive notification system with in-app and email delivery.
Features
- 🔔 In-app notifications with real-time updates
- 📧 Email notifications with digest support (instant, hourly, daily, weekly)
- 🎯 Fine-grained preferences - users control what they receive
- ⚡ Lightweight polling - updates every 30 seconds without performance impact
- 📱 Modern UI - notification bell with dropdown list
- 🎨 Multiple notification types - comments, likes, subscribes, uploads, mentions, system
Setup
1. Database Migration
Run the migration to create notification tables:
docker-compose exec db mysql -u easystream -peasystream easystream < __install/migrations/002_add_notifications_system.sql
This creates:
db_notifications- stores all notificationsdb_notification_preferences- user notification settingsdb_email_queue- queued emails for batched sending
2. Add Notification Bell to Header
Include the JavaScript and add the widget container to your header template:
<!-- In your header template -->
<script src="/f_scripts/fe/js/notifications.js"></script>
<!-- Add the notification bell to your navigation -->
<div id="notification-widget"></div>
The widget will auto-initialize and start polling for updates.
3. Configure Email Sending
By default, the system uses PHP's mail() function. For production, integrate with a proper email service:
Edit f_core/f_classes/class.notifications.php → sendEmail() method to integrate:
- SendGrid
- Mailgun
- Amazon SES
- SMTP
4. Set Up Cron Job
Add a cron job to process the email queue:
# Process email queue every 15 minutes
*/15 * * * * cd /srv/easystream && php app_scripts/cron/process_notification_emails.php >> /var/log/notification-emails.log 2>&1
Usage
Triggering Notifications
Use the VNotificationTriggers helper class for common events:
require_once 'f_core/f_classes/class.notification_triggers.php';
$triggers = new VNotificationTriggers();
// When someone comments on a video
$triggers->notifyVideoComment(
$video_owner_id,
$commenter_id,
$commenter_name,
$video_id,
$video_title,
$comment_text
);
// When someone likes a video
$triggers->notifyVideoLike(
$video_owner_id,
$liker_id,
$liker_name,
$video_id,
$video_title
);
// When someone subscribes
$triggers->notifySubscribe(
$channel_owner_id,
$subscriber_id,
$subscriber_name
);
// When uploading a new video (notifies all subscribers)
$triggers->notifySubscribersNewVideo(
$channel_owner_id,
$channel_name,
$video_id,
$video_title
);
// When mentioning a user in a comment
$triggers->notifyMention(
$mentioned_user_id,
$mentioner_id,
$mentioner_name,
$video_id,
$comment_text
);
// System notification
$triggers->notifySystem(
$user_id,
'Your video was approved',
'Your video "Tutorial" is now live!',
'/watch?v=123'
);
API Endpoints
Get Notifications
GET /api/notifications.php?limit=20&offset=0&unread_only=true
Response:
{
"success": true,
"data": {
"notifications": [
{
"notification_id": 123,
"type": "comment",
"title": "John commented on your video",
"message": "Great tutorial!",
"link": "/watch?v=456#comments",
"is_read": 0,
"created_at": "2026-03-30 17:30:00",
"actor_username": "john",
"actor_avatar": "/avatars/john.jpg"
}
],
"unread_count": 5
}
}
Get Unread Count (Lightweight)
GET /api/notifications_count.php
Response:
{
"success": true,
"unread_count": 5
}
Mark as Read
POST /api/notifications.php?action=mark_read
Content-Type: application/json
{"id": 123}
Or mark multiple:
{"id": "123,124,125"}
Mark All as Read
POST /api/notifications.php?action=mark_all_read
Delete Notification
DELETE /api/notifications.php?id=123
User Preferences
Get Preferences
GET /api/notification_preferences.php
Response:
{
"success": true,
"data": {
"email_comments": 1,
"email_likes": 1,
"email_subscribes": 1,
"email_uploads": 1,
"email_mentions": 1,
"email_digest": "instant",
"push_enabled": 1,
"push_comments": 1,
"push_likes": 1,
"push_subscribes": 1
}
}
Update Preferences
POST /api/notification_preferences.php
Content-Type: application/json
{
"email_digest": "daily",
"email_likes": 0,
"push_comments": 1
}
Email Digest Modes
Users can choose how often they receive emails:
- instant - Send immediately when notification is created
- hourly - Batch and send once per hour
- daily - Send once per day (at midnight)
- weekly - Send once per week (Monday)
- never - No email notifications
Notification Types
| Type | Description | Example |
|---|---|---|
comment |
New comment on video or reply to comment | "John commented on your video" |
like |
Someone liked a video | "Sarah liked your video" |
subscribe |
New channel subscriber | "Mike subscribed to your channel" |
video_upload |
Subscribed channel uploaded new video | "TechChannel uploaded a new video" |
mention |
User mentioned in a comment | "@username mentioned you" |
system |
Admin/system messages | "Your video was approved" |
Frontend Integration
The notification widget provides:
- Real-time bell icon with unread count badge
- Dropdown with last 20 notifications
- Mark individual or all as read
- Auto-polling every 30 seconds
- Click notification to navigate and mark as read
JavaScript API
// Access the widget instance
const widget = window.notificationWidget;
// Manually fetch notifications
widget.fetchNotifications();
// Manually fetch unread count
widget.fetchUnreadCount();
// Change poll interval (milliseconds)
widget.pollInterval = 60000; // 1 minute
widget.stopPolling();
widget.startPolling();
// Programmatically mark as read
widget.markAsRead(notification_id);
// Mark all as read
widget.markAllAsRead();
Database Schema
db_notifications
| Column | Type | Description |
|---|---|---|
| notification_id | INT | Primary key |
| usr_id | INT | User receiving notification |
| type | ENUM | Notification type |
| title | VARCHAR(255) | Short title |
| message | TEXT | Optional detailed message |
| link | VARCHAR(512) | URL to navigate to |
| actor_id | INT | User who triggered notification |
| related_video_id | INT | Associated video |
| related_comment_id | INT | Associated comment |
| is_read | TINYINT | Read status |
| is_seen | TINYINT | Seen status (viewed in UI) |
| email_sent | TINYINT | Email delivery status |
| created_at | DATETIME | Creation timestamp |
| read_at | DATETIME | When marked as read |
db_notification_preferences
| Column | Type | Description |
|---|---|---|
| usr_id | INT | User ID (unique) |
| email_comments | TINYINT | Email for comments |
| email_likes | TINYINT | Email for likes |
| email_subscribes | TINYINT | Email for subscribes |
| email_uploads | TINYINT | Email for new uploads |
| email_mentions | TINYINT | Email for mentions |
| email_digest | ENUM | Digest frequency |
| push_enabled | TINYINT | Push notifications enabled |
| push_comments | TINYINT | Push for comments |
| push_likes | TINYINT | Push for likes |
| push_subscribes | TINYINT | Push for subscribes |
db_email_queue
| Column | Type | Description |
|---|---|---|
| queue_id | INT | Primary key |
| usr_id | INT | Recipient user |
| VARCHAR(255) | Email address | |
| subject | VARCHAR(255) | Email subject |
| body | TEXT | Email HTML body |
| scheduled_for | DATETIME | When to send |
| sent_at | DATETIME | Actual send time |
| attempts | INT | Send attempts |
| last_error | TEXT | Last error message |
Performance
- Polling: Lightweight count query every 30s (~1-2ms)
- Dropdown: Full notification list only when opened
- Email queue: Batched processing reduces database load
- Auto-cleanup: Old read notifications purged after 90 days
Cleanup
The cron script automatically cleans up:
- Read notifications older than 90 days
- Sent emails from queue
Adjust retention in app_scripts/cron/process_notification_emails.php:
$notifications->cleanup(90); // Days to keep
Troubleshooting
Notifications not appearing
- Check database tables exist
- Verify JavaScript is loaded:
window.notificationWidget - Check browser console for errors
- Verify user is authenticated (session active)
Emails not sending
- Check cron job is running
- View email queue:
SELECT * FROM db_email_queue WHERE sent_at IS NULL - Check email configuration in
class.notifications.php - View logs:
docker-compose logs php | grep email
High polling load
Increase poll interval in the frontend:
// In notifications.js
this.pollInterval = 60000; // 60 seconds instead of 30
Future Enhancements
- WebSocket/SSE for true real-time updates (eliminate polling)
- Push notifications (web push API)
- Mobile app push notifications (FCM/APNS)
- Notification grouping (e.g., "5 people liked your video")
- Rich notification preview with thumbnails
- Notification filtering/search
- Mark as unread
- Snooze notifications