bmc_hub/REMINDER_SYSTEM_IMPLEMENTATION.md
Christian b43e9f797d feat: Add reminder system for sag cases with user preferences and notification channels
- Implemented user notification preferences table for managing default notification settings.
- Created sag_reminders table to define reminder rules with various trigger types and recipient configurations.
- Developed sag_reminder_queue for processing reminder events triggered by status changes or scheduled times.
- Added sag_reminder_logs to track reminder notifications and user interactions.
- Introduced frontend notification system using Bootstrap 5 Toast for displaying reminders.
- Created email template for sending reminders with case details and action links.
- Implemented rate limiting for user notifications to prevent spamming.
- Added triggers and functions for automatic updates and reminder processing.
2026-02-06 10:47:14 +01:00

387 lines
12 KiB
Markdown

# Reminder System Implementation - BMC Hub
## Overview
The Reminder System for BMC Hub's Sag (Case) module provides flexible, multi-channel notification delivery with support for:
- **Time-based reminders**: Scheduled at specific times or recurring (daily, weekly, monthly)
- **Status-change triggered reminders**: Automatically triggered when case status changes
- **Multi-channel delivery**: Mattermost, Email, Frontend popup notifications
- **User preferences**: Global defaults with per-reminder overrides
- **Rate limiting**: Max 5 notifications per user per hour (global)
- **Smart scheduling**: Database triggers for status changes, APScheduler for time-based
## Architecture
### Database Schema
**4 Main Tables** (created in `migrations/096_reminder_system.sql`):
1. **`user_notification_preferences`** - User default notification settings
- Default channels (mattermost, email, frontend)
- Quiet hours configuration
- Email override option
2. **`sag_reminders`** - Reminder rules/templates
- Trigger configuration (status_change, deadline_approaching, time_based)
- Recipient configuration (user IDs or email addresses)
- Recurrence setup (once, daily, weekly, monthly)
- Scheduling info (scheduled_at, next_check_at)
3. **`sag_reminder_queue`** - Event queue from database triggers
- Holds events generated by status-change trigger
- Processing status tracking (pending, processing, sent, failed, rate_limited)
- Batch processed by scheduler job
4. **`sag_reminder_logs`** - Execution log
- Every notification sent/failed is logged
- User interactions (snooze, dismiss, acknowledge)
- Used for rate limiting verification
**Database Triggers**:
- `sag_status_change_reminder_trigger()` - Fires on status UPDATE, queues relevant reminders
**Helper Functions**:
- `check_reminder_rate_limit(user_id)` - Verifies user hasn't exceeded 5 per hour
**Helper Views**:
- `v_pending_reminders` - Time-based reminders ready to send
- `v_pending_reminder_queue` - Queued events ready for processing
### Backend Services
**`app/services/reminder_notification_service.py`**:
- Unified notification delivery via Mattermost, Email, Frontend
- Merges user preferences with per-reminder overrides
- Rate limit checking
- Event logging
- Email template rendering (Jinja2)
**`app/services/email_service.py`** (extended):
- Added `send_email()` async method using `aiosmtplib`
- SMTP configuration from `.env`
- Supports plain text + HTML bodies
- Safety flag: `REMINDERS_DRY_RUN=true` logs without sending
### API Endpoints
**User Preferences** (in `app/modules/sag/backend/reminders.py`):
```
GET /api/v1/users/me/notification-preferences
PATCH /api/v1/users/me/notification-preferences
```
**Reminder CRUD**:
```
GET /api/v1/sag/{sag_id}/reminders - List reminders for case
POST /api/v1/sag/{sag_id}/reminders - Create new reminder
PATCH /api/v1/sag/reminders/{reminder_id} - Update reminder
DELETE /api/v1/sag/reminders/{reminder_id} - Soft-delete reminder
```
**Reminder Interactions**:
```
POST /api/v1/sag/reminders/{reminder_id}/snooze - Snooze for X minutes
POST /api/v1/sag/reminders/{reminder_id}/dismiss - Permanently dismiss
GET /api/v1/reminders/pending/me - Get pending (for polling)
```
### Scheduler Job
**`app/jobs/check_reminders.py`**:
- Processes time-based reminders (`next_check_at <= NOW()`)
- Processes queued status-change events
- Calculates next recurrence (`daily` +24h, `weekly` +7d, `monthly` +30d)
- Respects rate limiting
**Registration in `main.py`**:
```python
backup_scheduler.scheduler.add_job(
func=check_reminders,
trigger=IntervalTrigger(minutes=5),
id='check_reminders',
name='Check Reminders',
max_instances=1
)
```
Runs **every 5 minutes** (configurable via `REMINDERS_CHECK_INTERVAL_MINUTES`)
### Frontend Notifications
**`static/js/notifications.js`**:
- Bootstrap 5 Toast-based notification popups
- Polls `/api/v1/reminders/pending/me` every 30 seconds
- Snooze presets: 15min, 30min, 1h, 4h, 1day, custom
- Dismiss action
- Auto-removes when hidden
- Pauses polling when tab not visible
**Integration**:
- Loaded in `app/shared/frontend/base.html`
- Auto-initializes on page load if user authenticated
- User ID extracted from JWT token
## Configuration
### Environment Variables
```env
# Master switches (default: disabled for safety)
REMINDERS_ENABLED=false
REMINDERS_EMAIL_ENABLED=false
REMINDERS_MATTERMOST_ENABLED=false
REMINDERS_DRY_RUN=true # Log without sending if true
# Scheduler settings
REMINDERS_CHECK_INTERVAL_MINUTES=5 # Frequency of reminder checks
REMINDERS_MAX_PER_USER_PER_HOUR=5 # Rate limit
REMINDERS_QUEUE_BATCH_SIZE=10 # Batch size for queue processing
# SMTP Configuration (for email reminders)
EMAIL_SMTP_HOST=smtp.gmail.com
EMAIL_SMTP_PORT=587
EMAIL_SMTP_USER=noreply@bmcnetworks.dk
EMAIL_SMTP_PASSWORD=<secret>
EMAIL_SMTP_USE_TLS=true
EMAIL_SMTP_FROM_ADDRESS=noreply@bmcnetworks.dk
EMAIL_SMTP_FROM_NAME=BMC Hub
```
### Pydantic Configuration
Added to `app/core/config.py`:
```python
REMINDERS_ENABLED: bool = False
REMINDERS_EMAIL_ENABLED: bool = False
REMINDERS_MATTERMOST_ENABLED: bool = False
REMINDERS_DRY_RUN: bool = True
REMINDERS_CHECK_INTERVAL_MINUTES: int = 5
REMINDERS_MAX_PER_USER_PER_HOUR: int = 5
REMINDERS_QUEUE_BATCH_SIZE: int = 10
EMAIL_SMTP_HOST: str = ""
EMAIL_SMTP_PORT: int = 587
EMAIL_SMTP_USER: str = ""
EMAIL_SMTP_PASSWORD: str = ""
EMAIL_SMTP_USE_TLS: bool = True
EMAIL_SMTP_FROM_ADDRESS: str = "noreply@bmcnetworks.dk"
EMAIL_SMTP_FROM_NAME: str = "BMC Hub"
```
## Safety Features
### Rate Limiting
- **Global per user**: Max 5 notifications per hour
- Checked before sending via `check_reminder_rate_limit(user_id)`
- Queued events marked as `rate_limited` if limit exceeded
### Dry Run Mode
- `REMINDERS_DRY_RUN=true` (default)
- All operations logged to console/logs
- No emails actually sent
- No Mattermost webhooks fired
- Useful for testing
### Soft Delete
- Reminders never hard-deleted from DB
- `deleted_at` timestamp + `is_active=false`
- Full audit trail preserved
### Per-Reminder Override
- Reminder can override user's default channels
- `override_user_preferences` flag
- Useful for critical reminders (urgent priority)
## Usage Examples
### Create a Status-Change Reminder
```bash
curl -X POST http://localhost:8000/api/v1/sag/123/reminders \
-H "Content-Type: application/json" \
-d {
"title": "Case entered In Progress",
"message": "Case #123 has moved to 'i_gang' status",
"priority": "high",
"trigger_type": "status_change",
"trigger_config": {"target_status": "i_gang"},
"recipient_user_ids": [1, 2],
"notify_mattermost": true,
"notify_email": true,
"recurrence_type": "once"
}
```
### Create a Scheduled Reminder
```bash
curl -X POST http://localhost:8000/api/v1/sag/123/reminders \
-H "Content-Type: application/json" \
-d {
"title": "Follow up needed",
"message": "Check in with customer",
"priority": "normal",
"trigger_type": "time_based",
"trigger_config": {},
"scheduled_at": "2026-02-10T14:30:00",
"recipient_user_ids": [1],
"recurrence_type": "once"
}
```
### Create a Daily Recurring Reminder
```bash
curl -X POST http://localhost:8000/api/v1/sag/123/reminders \
-H "Content-Type: application/json" \
-d {
"title": "Daily status check",
"priority": "low",
"trigger_type": "time_based",
"trigger_config": {},
"scheduled_at": "2026-02-04T09:00:00",
"recipient_user_ids": [1],
"recurrence_type": "daily"
}
```
### Update User Preferences
```bash
curl -X PATCH http://localhost:8000/api/v1/users/me/notification-preferences \
-H "Content-Type: application/json" \
-d {
"notify_mattermost": true,
"notify_email": false,
"notify_frontend": true,
"quiet_hours_enabled": true,
"quiet_hours_start": "18:00",
"quiet_hours_end": "08:00"
}
```
## Testing Checklist
### Database
- [ ] Run migration: `docker-compose exec -T postgres psql -U bmc_hub -d bmc_hub -f /migrations/096_reminder_system.sql`
- [ ] Verify tables created: `SELECT * FROM sag_reminders LIMIT 0;`
- [ ] Verify trigger exists: `SELECT * FROM information_schema.triggers WHERE trigger_name LIKE 'sag%reminder%';`
### API
- [ ] Test create reminder endpoint
- [ ] Test list reminders endpoint
- [ ] Test update reminder endpoint
- [ ] Test snooze endpoint
- [ ] Test dismiss endpoint
- [ ] Test user preferences endpoints
### Scheduler
- [ ] Enable `REMINDERS_ENABLED=true` in `.env`
- [ ] Restart container
- [ ] Check logs for "Reminder job scheduled" message
- [ ] Verify job runs every 5 minutes: `✅ Checking for pending reminders...`
### Status Change Trigger
- [ ] Create reminder with `trigger_type: status_change`
- [ ] Change case status
- [ ] Verify event inserted in `sag_reminder_queue`
- [ ] Wait for scheduler to process
- [ ] Verify log entry in `sag_reminder_logs`
### Email Sending
- [ ] Configure SMTP in `.env`
- [ ] Set `REMINDERS_EMAIL_ENABLED=true`
- [ ] Set `REMINDERS_DRY_RUN=false`
- [ ] Create reminder with `notify_email=true`
- [ ] Verify email sent or check logs
### Frontend Popup
- [ ] Ensure `static/js/notifications.js` included in base.html
- [ ] Open browser console
- [ ] Log in to system
- [ ] Should see "✅ Reminder system initialized"
- [ ] Create a pending reminder
- [ ] Should see popup toast within 30 seconds
- [ ] Test snooze dropdown
- [ ] Test dismiss button
### Rate Limiting
- [ ] Create 6 reminders for user with trigger_type=time_based
- [ ] Manually trigger scheduler job
- [ ] Verify only 5 sent, 1 marked `rate_limited`
- [ ] Check `sag_reminder_logs` for status
## Deployment Notes
### Local Development
- All safety switches OFF by default (`_ENABLED=false`, `DRY_RUN=true`)
- No SMTP configured - reminders won't send
- No Mattermost webhook - notifications go to logs only
- Test via dry-run mode
### Production Deployment
1. Configure SMTP credentials in `.env`
2. Set `REMINDERS_ENABLED=true`
3. Set `REMINDERS_EMAIL_ENABLED=true` if using email
4. Set `REMINDERS_MATTERMOST_ENABLED=true` if using Mattermost
5. Set `REMINDERS_DRY_RUN=false` to actually send
6. Deploy with `docker-compose -f docker-compose.prod.yml up -d --build`
7. Monitor logs for errors: `docker-compose logs -f api`
## Files Modified/Created
### New Files
- `migrations/096_reminder_system.sql` - Database schema
- `app/services/reminder_notification_service.py` - Notification service
- `app/jobs/check_reminders.py` - Scheduler job
- `app/modules/sag/backend/reminders.py` - API endpoints
- `static/js/notifications.js` - Frontend notification system
- `templates/emails/reminder.html` - Email template
### Modified Files
- `app/core/config.py` - Added reminder settings
- `app/services/email_service.py` - Added `send_email()` method
- `main.py` - Imported reminders router, registered scheduler job
- `app/shared/frontend/base.html` - Added notifications.js script tag
- `requirements.txt` - Added `aiosmtplib` dependency
- `.env` - Added reminder configuration
## Troubleshooting
### Reminders not sending
1. Check `REMINDERS_ENABLED=true` in `.env`
2. Check scheduler logs: "Reminder check complete"
3. Verify `next_check_at` <= NOW() for reminders
4. Check rate limit: count in `sag_reminder_logs` last hour
### Frontend popups not showing
1. Check browser console for errors
2. Verify JWT token contains `sub` (user_id)
3. Check `GET /api/v1/reminders/pending/me` returns data
4. Ensure `static/js/notifications.js` loaded
### Email not sending
1. Verify SMTP credentials in `.env`
2. Check `REMINDERS_EMAIL_ENABLED=true`
3. Check `REMINDERS_DRY_RUN=false`
4. Review application logs for SMTP errors
5. Test SMTP connection separately
### Database trigger not working
1. Verify migration applied successfully
2. Check `sag_status_change_reminder_trigger_exec` trigger exists
3. Update case status manually
4. Check `sag_reminder_queue` for new events
5. Review PostgreSQL logs if needed
## Future Enhancements
- [ ] Escalation rules (auto-escalate if not acknowledged)
- [ ] SMS/WhatsApp integration (Twilio)
- [ ] Calendar integration (iCal export)
- [ ] User notification history/statistics
- [ ] Webhook support for external services
- [ ] AI-powered reminder suggestions
- [ ] Mobile app push notifications