19 KiB
Notifications Module
Last Verified: January 20, 2026
Version: 1.8.4
Status: ✅ Active
Backend Path: backend/igny8_core/business/notifications/
Frontend Path: frontend/src/store/notificationStore.ts, frontend/src/components/header/NotificationDropdown.tsx, frontend/src/pages/account/NotificationsPage.tsx
Quick Reference
| What | File | Key Items |
|---|---|---|
| Models | business/notifications/models.py |
Notification, NotificationPreference |
| Views | business/notifications/views.py |
NotificationViewSet |
| Serializers | business/notifications/serializers.py |
NotificationSerializer |
| Services | business/notifications/services.py |
NotificationService |
| Frontend Store | store/notificationStore.ts |
Zustand notification store |
| Frontend Dropdown | components/header/NotificationDropdown.tsx |
Notification UI (header bell) |
| Frontend Page | pages/account/NotificationsPage.tsx |
Full notifications history page |
| API Client | services/notifications.api.ts |
API methods |
Purpose
The Notifications module provides real-time user notifications for AI operations, system events, and workflow milestones. Notifications appear in the header dropdown and persist in the database.
Key Features:
- Real-time notifications for AI task completion/failure
- Account-wide or user-specific notifications
- Read/unread state tracking
- Action buttons with navigation
- Auto-fetch and auto-sync with API
Data Models
Notification
| Field | Type | Currently Used? | Purpose |
|---|---|---|---|
| account | FK | ✅ Yes | Owner account (required for multi-tenancy) |
| user | FK | ❌ No | User-specific notifications (nullable - if null, visible to all account users). Currently unused - all notifications are account-wide |
| notification_type | CharField | ✅ Yes | Type from NotificationType choices (for filtering, icons) |
| title | CharField(200) | ✅ Yes | Notification title shown in dropdown |
| message | TextField | ✅ Yes | Notification body text shown in dropdown |
| severity | CharField | ✅ Yes | info/success/warning/error (affects icon color) |
| site | FK | ✅ Partial | Related site (nullable). Created but not displayed in current dropdown |
| content_type | FK | ❌ No | Generic relation to any object. Future: Link notification to specific Content/Task/etc. for direct access |
| object_id | PositiveInteger | ❌ No | Generic relation ID. Future: Used with content_type for object linking |
| action_url | CharField(500) | ✅ Yes | Frontend route for action button (e.g., /planner/clusters) |
| action_label | CharField(50) | ✅ Yes | Action button text (e.g., "View Clusters") |
| is_read | Boolean | ✅ Yes | Read status (default: False). Used for unread badge count |
| read_at | DateTime | ✅ Partial | When marked read (nullable). Created but not displayed in dropdown |
| metadata | JSON | ✅ Partial | Additional data (counts, details). Stored but not displayed in dropdown |
| created_at | DateTime | ✅ Yes | Creation timestamp. Shown as relative time ("2 minutes ago") |
| updated_at | DateTime | ❌ No | Last update timestamp. Currently unused |
Indexes:
(account, -created_at)- List notifications by account ✅ Used(account, is_read, -created_at)- Filter unread ✅ Used(user, -created_at)- User-specific notifications ❌ Unused (all notifications are account-wide)
Why So Many Unused Fields?
The model was designed with future expansion in mind, but the current "dropdown-only" implementation uses less than half of the fields. Here's why they exist:
Over-Engineered for Current Use:
content_type+object_id→ For direct object linking (planned: click notification, go to that exact content item)user→ For user-specific notifications (planned: "@John, your content is ready")metadata→ For rich data (planned: show counts, progress, details in dedicated page)read_at→ For analytics (planned: "You read this 2 days ago")updated_at→ For editing notifications (planned: update notification instead of creating duplicate)
Currently Essential:
account,title,message,severity,notification_type→ Core notification dataaction_url,action_label→ Navigation buttonsis_read,created_at→ Dropdown functionality
The Design Mismatch: You're right - this is a database schema designed for a full-featured notification system (with dedicated page, filtering, search, history) but the frontend only implements a simple dropdown. The backend is over-built for the current use case.
Notification Types
AI Operations
| Type | Severity | Trigger | Message Format |
|---|---|---|---|
ai_cluster_complete |
success | Clustering finishes | "Created X clusters from Y keywords" |
ai_cluster_failed |
error | Clustering fails | "Failed to cluster keywords: [error]" |
ai_ideas_complete |
success | Idea generation finishes | "Generated X content ideas from Y clusters" |
ai_ideas_failed |
error | Idea generation fails | "Failed to generate ideas: [error]" |
ai_content_complete |
success | Content generation finishes | "Generated X articles (Y words)" |
ai_content_failed |
error | Content generation fails | "Failed to generate content: [error]" |
ai_images_complete |
success | Image generation finishes | "Generated X images" |
ai_images_failed |
error | Image generation fails | "Failed to generate X images: [error]" |
ai_prompts_complete |
success | Image prompts created | "X image prompts ready (1 featured + Y in-article)" |
ai_prompts_failed |
error | Image prompt creation fails | "Failed to create image prompts: [error]" |
Workflow Events
| Type | Severity | Trigger | Message Format |
|---|---|---|---|
content_ready_review |
info | Content moved to review | "[Title] ready for review" |
content_published |
success | Content published | '"[Title]" published to [site]' |
content_publish_failed |
error | Publishing fails | 'Failed to publish "[Title]": [error]' |
WordPress Sync
| Type | Severity | Trigger | Message Format |
|---|---|---|---|
wordpress_sync_success |
success | Sync completes | "Synced X items with [site]" |
wordpress_sync_failed |
error | Sync fails | "WordPress sync failed for [site]: [error]" |
Credits & Billing
| Type | Severity | Trigger | Message Format |
|---|---|---|---|
credits_low |
warning | Credits < 20% | "You've used X% of your credits. Y remaining." |
credits_depleted |
error | Credits exhausted | "Your credits are exhausted. Upgrade to continue." |
Setup & System
| Type | Severity | Trigger | Message Format |
|---|---|---|---|
site_setup_complete |
success | All setup steps done | "[Site] is fully configured and ready!" |
keywords_imported |
info | Keywords added | "Added X keywords to [site]" |
system_info |
info | System messages | Custom message |
Notification Triggers
AI Task Completion (AIEngine)
Location: backend/igny8_core/ai/engine.py
Methods: _create_success_notification(), _create_failure_notification()
Flow:
- AIEngine executes AI function (
auto_cluster,generate_ideas, etc.) - On success (after DONE phase):
- Calls
_create_success_notification(function_name, save_result, payload) - Maps function to NotificationService method
- Creates notification with counts/metrics
- Calls
- On failure (in
_handle_error()):- Calls
_create_failure_notification(function_name, error) - Creates error notification with error message
- Calls
Mapping:
auto_cluster → NotificationService.notify_clustering_complete/failed
generate_ideas → NotificationService.notify_ideas_complete/failed
generate_content → NotificationService.notify_content_complete/failed
generate_image_prompts → NotificationService.notify_prompts_complete/failed
generate_images → NotificationService.notify_images_complete/failed
WordPress Publishing
Location: backend/igny8_core/tasks/wordpress_publishing.py
Trigger: After publishing content to WordPress
NotificationService.notify_content_published(
account=account,
site=site,
title=content.title,
content_object=content
)
WordPress Sync
Location: backend/igny8_core/tasks/wordpress_publishing.py
Trigger: After syncing posts from WordPress
NotificationService.notify_wordpress_sync_success(
account=account,
site=site,
count=synced_count
)
Credit Alerts
Location: backend/igny8_core/business/billing/services/credit_service.py
Trigger: After deducting credits
# When credits drop below threshold
NotificationService.notify_credits_low(
account=account,
percentage_used=80,
credits_remaining=remaining
)
# When credits exhausted
NotificationService.notify_credits_depleted(account=account)
Manual Triggers (Optional)
Notifications can also be created manually from anywhere:
from igny8_core.business.notifications.services import NotificationService
NotificationService.notify_keywords_imported(
account=account,
site=site,
count=keyword_count
)
Note: As of v1.2.1, the following actions DO create notifications:
- ✅ AI task completion/failure (clustering, ideas, content, images, prompts)
- ✅ Keyword import via "Add to Workflow" - Fixed in v1.2.1
Actions that DON'T yet create notifications (planned):
- ❌ WordPress publishing (needs integration in wordpress_publishing.py)
- ❌ WordPress sync (needs integration in wordpress_publishing.py)
- ❌ Credit alerts (needs integration in credit_service.py)
- ❌ Automation completion (planned for future)
User Interface
Notification Dropdown (Header)
Location: Header bell icon (top right)
File: frontend/src/components/header/NotificationDropdown.tsx
Features:
- Shows last 50 notifications
- Animated badge with unread count
- Click notification to mark read + navigate
- "Mark all read" button
- Auto-refreshes every 30 seconds
- Refreshes when opened (if stale > 1 minute)
- "View All Notifications" link to full page
Notifications Page (Full History)
Location: /account/notifications
File: frontend/src/pages/account/NotificationsPage.tsx
Access: Sidebar → ACCOUNT → Notifications OR NotificationDropdown → "View All Notifications"
Features:
- Filters:
- Severity (info/success/warning/error)
- Notification type (AI operations, WordPress sync, credits, etc.)
- Read status (all/unread/read)
- Site (filter by specific site)
- Date range (from/to dates)
- Actions:
- Mark individual notifications as read
- Mark all as read (bulk action)
- Delete individual notifications
- Click notification to navigate to action URL
- Display:
- Full notification history with pagination
- Severity icons with color coding
- Relative timestamps ("2 hours ago")
- Site badge when applicable
- Action buttons for related pages
- Unread badge in sidebar menu
v1.2.2 Implementation:
- ✅ Full-page notifications view created
- ✅ Advanced filtering by severity, type, read status, site, date range
- ✅ Bulk actions (mark all read)
- ✅ Individual actions (mark read, delete)
- ✅ Added to sidebar under ACCOUNT section
- ✅ Unread count badge in sidebar
- ✅ Fixed broken link in NotificationDropdown (was
/notifications, now/account/notifications)
API Endpoints
List Notifications
GET /api/v1/notifications/
Query Parameters:
page- Page number (default: 1)page_size- Results per page (default: 20)is_read- Filter by read status (true/false)notification_type- Filter by type (e.g.,ai_cluster_complete)severity- Filter by severity (info/success/warning/error)
Response:
{
"count": 42,
"next": "/api/v1/notifications/?page=2",
"previous": null,
"results": [
{
"id": 123,
"notification_type": "ai_cluster_complete",
"severity": "success",
"title": "Clustering Complete",
"message": "Created 5 clusters from 50 keywords",
"is_read": false,
"created_at": "2025-12-28T10:30:00Z",
"read_at": null,
"action_label": "View Clusters",
"action_url": "/planner/clusters",
"site": {"id": 1, "name": "My Blog"},
"metadata": {
"cluster_count": 5,
"keyword_count": 50
}
}
]
}
Get Unread Count
GET /api/v1/notifications/unread-count/
Response:
{
"unread_count": 7
}
Mark as Read
POST /api/v1/notifications/{id}/read/
Response:
{
"id": 123,
"is_read": true,
"read_at": "2025-12-28T10:35:00Z"
}
Mark All as Read
POST /api/v1/notifications/read-all/
Response:
{
"updated_count": 7,
"message": "Marked 7 notifications as read"
}
Delete Notification
DELETE /api/v1/notifications/{id}/
Response: 204 No Content
Frontend Implementation
Notification Store
File: frontend/src/store/notificationStore.ts
Features:
- Zustand store for state management
- In-memory queue for optimistic UI updates
- API sync for persistent notifications
- Auto-fetch on mount and periodic sync (every 30 seconds)
- Auto-refresh when dropdown opens (if stale > 1 minute)
Key Methods:
// Add local notification (optimistic)
addNotification(notification)
// Mark as read (optimistic + API sync)
markAsRead(id)
// Mark all as read
markAllAsRead()
// Fetch from API
fetchNotifications()
// Sync unread count
syncUnreadCount()
Notification Dropdown
File: frontend/src/components/header/NotificationDropdown.tsx
Features:
- Badge with unread count (animated ping when > 0)
- Dropdown with notification list
- Click notification to mark read and navigate
- "Mark all read" action
- Empty state when no notifications
- Auto-fetch on open if stale
Icon Mapping:
auto_cluster → GroupIcon
generate_ideas → BoltIcon
generate_content → FileTextIcon
generate_images → FileIcon
system → AlertIcon
success → CheckCircleIcon
Business Logic
Visibility Rules
-
Account-wide notifications (
user=NULL):- Visible to ALL users in the account
- Example: "Automation completed 10 tasks"
-
User-specific notifications (
user=User):- Only visible to that specific user
- Example: "Your content is ready for review"
-
Site-filtered (frontend):
- Frontend can filter by site in UI
- Backend always returns all account notifications
Read Status
- Notifications start as
is_read=False - Clicking notification marks it read (API call + optimistic update)
- "Mark all read" bulk updates all unread notifications
- Read notifications stay in dropdown (can be filtered out in future)
Retention Policy
- Notifications never auto-delete (future: add retention policy)
- Users can manually delete notifications
- Admin can clean up old notifications via management command (future)
Common Issues
| Issue | Cause | Fix |
|---|---|---|
| Notifications not appearing | AIEngine not calling NotificationService | Fixed in v1.2.1 |
| "Add to workflow" no notification | KeywordViewSet not calling NotificationService | Fixed in v1.2.1 |
| Can't see notification history | No dedicated notifications page | Fixed in v1.2.2 - Page created at /account/notifications |
| "View All" button → 404 | Link to /notifications but page doesn't exist |
Fixed in v1.2.2 - Link updated to /account/notifications |
| Duplicate notifications | Multiple AI task retries | Check task retry logic |
| Missing notifications | Celery worker crashed | Check Celery logs |
| Unread count wrong | Race condition in state | Refresh page or wait for sync |
| Action URL not working | Incorrect route in action_url | Check NotificationService methods |
Integration Points
| From | To | Trigger |
|---|---|---|
| AIEngine | NotificationService | AI task success/failure |
| WordPress Publisher | NotificationService | Publishing/sync events |
| Credit Service | NotificationService | Low credits/depleted |
| Automation | NotificationService | Automation milestones (future) |
Planned Changes
| Feature | Status | Description |
|---|---|---|
| Notification preferences | 🔜 Planned | User can toggle notification types |
| Email notifications | 🔜 Planned | Send email for critical notifications |
| Push notifications | 🔜 Planned | Browser push for real-time alerts |
| Notification retention | 🔜 Planned | Auto-delete after 30/60 days |
| Notification categories | 🔜 Planned | Group by module (Planner, Writer, etc.) |
| Notification sounds | 🔜 Planned | Audio alerts for important events |
| Webhook notifications | 🔜 Planned | POST to external webhook URLs |
Version History
| Version | Date | Changes |
|---|---|---|
| v1.2.2 | Dec 28, 2025 | NEW: Full notifications page at /account/notifications with filtering, bulk actions, and sidebar integration |
| v1.2.1 | Dec 28, 2025 | Fixed: Notifications now created on AI task completion + keyword import |
| v1.2.0 | Dec 27, 2025 | Initial notifications system implementation |
Testing
Test Notification Creation
# In Django shell: docker exec -it igny8_backend python manage.py shell
from igny8_core.business.notifications.services import NotificationService
from igny8_core.auth.models import Account
account = Account.objects.first()
# Test clustering notification
NotificationService.notify_clustering_complete(
account=account,
cluster_count=5,
keyword_count=50
)
# Test error notification
NotificationService.notify_clustering_failed(
account=account,
error="Insufficient credits"
)
Test Frontend
- Run AI operation (clustering, idea generation, etc.)
- Check notification dropdown (bell icon in header)
- Verify unread badge appears
- Click notification to mark read and navigate
- Click "Mark all read" to clear badge
Debug Commands
# Check Celery logs for notification creation
docker logs igny8_celery_worker -f | grep -i "notification"
# Check notifications in database
docker exec -it igny8_backend python manage.py shell
>>> from igny8_core.business.notifications.models import Notification
>>> Notification.objects.count()
>>> Notification.objects.filter(is_read=False).count()
>>> Notification.objects.last().title
# Test notification API
curl -H "Authorization: Bearer <token>" \
http://localhost:8011/api/v1/notifications/
# Check notification creation code
grep -r "notify_clustering_complete" backend/
Architecture Notes
Design Pattern: Service Layer + Repository Pattern
NotificationServiceprovides static methods for creating notifications- Each notification type has dedicated method with validation
Notification.create_notification()is class method for low-level creation- Views use
AccountModelViewSetfor automatic account filtering
Why Not Signals?
- Explicit is better than implicit
- Clear call sites for debugging
- Easier to test and mock
- No hidden side effects
- Can pass context-specific data
Lazy Imports:
- NotificationService uses lazy imports in AIEngine to avoid Django app loading issues
- Import inside method, not at module level