Skip to content

Commit 1e892e6

Browse files
committed
chore: unified notification dispatcher
1 parent be357c7 commit 1e892e6

4 files changed

Lines changed: 845 additions & 0 deletions

File tree

Lines changed: 394 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,394 @@
1+
# Unified Notification Dispatcher Guide
2+
3+
## Overview
4+
5+
The Unified Notification Dispatcher provides a centralized system for sending notifications across multiple channels (in-app, push, email) with a single function call. It automatically routes to the appropriate email tasks and handles user preferences.
6+
7+
## Features
8+
9+
- **Multi-channel Support**: Send to in-app, push, and email simultaneously
10+
- **Automatic Routing**: Automatically routes emails to correct Celery tasks
11+
- **User Preferences**: Respects user notification preferences (in_app_enabled, push_enabled, email_enabled)
12+
- **Type Safety**: Uses enums for event types and channels
13+
- **Error Handling**: Comprehensive error handling and logging
14+
- **Flexible API**: Both detailed and quick-notify methods available
15+
16+
## Quick Start
17+
18+
### Basic Usage
19+
20+
```python
21+
from apps.notifications.services.dispatcher import (
22+
UnifiedNotificationDispatcher,
23+
NotificationChannel,
24+
NotificationEventType
25+
)
26+
27+
# Send notification across all channels
28+
result = UnifiedNotificationDispatcher.quick_notify(
29+
user=user,
30+
event_type=NotificationEventType.LOAN_APPROVED,
31+
title="Loan Approved!",
32+
message="Your loan application has been approved.",
33+
object_id=str(loan.application_id),
34+
channels=[NotificationChannel.IN_APP, NotificationChannel.PUSH, NotificationChannel.EMAIL],
35+
)
36+
```
37+
38+
## Available Event Types
39+
40+
### KYC Events
41+
- `KYC_APPROVED` - KYC verification approved
42+
- `KYC_PENDING` - KYC verification pending review
43+
- `KYC_REJECTED` - KYC verification rejected/action required
44+
45+
### Loan Events
46+
- `LOAN_APPROVED` - Loan application approved
47+
- `LOAN_DISBURSED` - Loan disbursed to wallet
48+
- `LOAN_REPAYMENT` - Loan repayment successful
49+
50+
### Investment Events
51+
- `INVESTMENT_CREATED` - Investment created successfully
52+
- `INVESTMENT_MATURED` - Investment has matured
53+
54+
### Card Events
55+
- `CARD_ISSUED` - Card issued to user
56+
57+
### Wallet Events
58+
- `WALLET_CREATED` - Wallet created successfully
59+
60+
### Bill Payment Events
61+
- `BILL_PAYMENT_SUCCESS` - Bill payment successful
62+
63+
### Transfer Events
64+
- `TRANSFER_SUCCESS` - Transfer successful
65+
66+
### Payment Events
67+
- `PAYMENT_SUCCESS` - Payment successful (for payer)
68+
- `PAYMENT_RECEIVED` - Payment received (for merchant)
69+
70+
## Available Channels
71+
72+
- `NotificationChannel.IN_APP` - In-app notification
73+
- `NotificationChannel.PUSH` - Push notification via FCM
74+
- `NotificationChannel.EMAIL` - Email notification
75+
76+
## Methods
77+
78+
### 1. `quick_notify()` - Simplified Method
79+
80+
Best for most use cases. Automatically maps object_id to the correct field.
81+
82+
```python
83+
UnifiedNotificationDispatcher.quick_notify(
84+
user=user,
85+
event_type=NotificationEventType.WALLET_CREATED,
86+
title="Wallet Created",
87+
message="Your USD wallet has been created successfully.",
88+
object_id=str(wallet.wallet_id),
89+
channels=[NotificationChannel.IN_APP, NotificationChannel.EMAIL],
90+
priority=NotificationPriority.MEDIUM,
91+
action_url="/wallets"
92+
)
93+
```
94+
95+
### 2. `dispatch()` - Advanced Method
96+
97+
For complex scenarios requiring custom context data.
98+
99+
```python
100+
UnifiedNotificationDispatcher.dispatch(
101+
user=user,
102+
event_type=NotificationEventType.PAYMENT_RECEIVED,
103+
channels=[NotificationChannel.IN_APP, NotificationChannel.PUSH, NotificationChannel.EMAIL],
104+
title="Payment Received",
105+
message=f"You received ${amount} from {payer_name}",
106+
context_data={
107+
"payment_id": str(payment.payment_id),
108+
"amount": str(amount),
109+
"payer_name": payer_name
110+
},
111+
priority=NotificationPriority.HIGH,
112+
related_object_type="Payment",
113+
related_object_id=str(payment.payment_id),
114+
action_url="/transactions",
115+
action_data={
116+
"payment_id": str(payment.payment_id),
117+
"reference": payment.reference
118+
}
119+
)
120+
```
121+
122+
## Usage Examples
123+
124+
### Example 1: KYC Approval Notification
125+
126+
```python
127+
from apps.notifications.services.dispatcher import (
128+
UnifiedNotificationDispatcher,
129+
NotificationChannel,
130+
NotificationEventType
131+
)
132+
from apps.notifications.models import NotificationPriority
133+
134+
# In your KYC approval service
135+
def approve_kyc(kyc_verification):
136+
# ... approval logic ...
137+
138+
# Send notification across all channels
139+
UnifiedNotificationDispatcher.quick_notify(
140+
user=kyc_verification.user,
141+
event_type=NotificationEventType.KYC_APPROVED,
142+
title="KYC Verified!",
143+
message="Your identity verification has been approved.",
144+
object_id=str(kyc_verification.kyc_id),
145+
channels=[
146+
NotificationChannel.IN_APP,
147+
NotificationChannel.PUSH,
148+
NotificationChannel.EMAIL
149+
],
150+
priority=NotificationPriority.HIGH,
151+
action_url="/dashboard"
152+
)
153+
```
154+
155+
### Example 2: Loan Disbursement
156+
157+
```python
158+
# In your loan processor
159+
async def disburse_loan(loan):
160+
# ... disbursement logic ...
161+
162+
# Notify user about disbursement
163+
result = UnifiedNotificationDispatcher.quick_notify(
164+
user=loan.user,
165+
event_type=NotificationEventType.LOAN_DISBURSED,
166+
title="Loan Disbursed",
167+
message=f"Your loan of ${loan.approved_amount:,.2f} has been credited to your wallet.",
168+
object_id=str(loan.application_id),
169+
channels=[NotificationChannel.IN_APP, NotificationChannel.EMAIL],
170+
)
171+
172+
logger.info(f"Loan disbursement notification sent: {result}")
173+
```
174+
175+
### Example 3: Investment Maturity
176+
177+
```python
178+
# In your investment processor
179+
def process_maturity(investment):
180+
# ... maturity processing ...
181+
182+
UnifiedNotificationDispatcher.quick_notify(
183+
user=investment.user,
184+
event_type=NotificationEventType.INVESTMENT_MATURED,
185+
title="Investment Matured!",
186+
message=f"Your investment has matured. Total payout: ${investment.total_payout:,.2f}",
187+
object_id=str(investment.investment_id),
188+
channels=[NotificationChannel.IN_APP, NotificationChannel.PUSH, NotificationChannel.EMAIL],
189+
priority=NotificationPriority.HIGH,
190+
action_url="/investments/portfolio"
191+
)
192+
```
193+
194+
### Example 4: Payment with Custom Data
195+
196+
```python
197+
# In your payment processor
198+
async def process_payment(payment):
199+
# ... payment processing ...
200+
201+
# Notify payer
202+
UnifiedNotificationDispatcher.dispatch(
203+
user=payment.payer_wallet.user,
204+
event_type=NotificationEventType.PAYMENT_SUCCESS,
205+
channels=[NotificationChannel.IN_APP, NotificationChannel.EMAIL],
206+
title="Payment Successful",
207+
message=f"Your payment of ${payment.amount:,.2f} was successful.",
208+
context_data={
209+
"payment_id": str(payment.payment_id),
210+
"amount": str(payment.amount),
211+
"merchant_name": payment.merchant_user.full_name
212+
},
213+
priority=NotificationPriority.MEDIUM,
214+
related_object_type="Payment",
215+
related_object_id=str(payment.payment_id),
216+
action_url="/transactions",
217+
action_data={
218+
"payment_id": str(payment.payment_id),
219+
"reference": payment.reference
220+
}
221+
)
222+
223+
# Notify merchant
224+
UnifiedNotificationDispatcher.quick_notify(
225+
user=payment.merchant_user,
226+
event_type=NotificationEventType.PAYMENT_RECEIVED,
227+
title="Payment Received",
228+
message=f"You received ${payment.net_amount:,.2f} from {payment.payer_name}.",
229+
object_id=str(payment.payment_id),
230+
channels=[NotificationChannel.IN_APP, NotificationChannel.PUSH],
231+
priority=NotificationPriority.HIGH,
232+
)
233+
```
234+
235+
### Example 5: Only Email Notification
236+
237+
```python
238+
# Send only email (no in-app or push)
239+
UnifiedNotificationDispatcher.quick_notify(
240+
user=user,
241+
event_type=NotificationEventType.CARD_ISSUED,
242+
title="Card Issued",
243+
message="Your new card has been issued.",
244+
object_id=str(card.card_id),
245+
channels=[NotificationChannel.EMAIL], # Only email
246+
)
247+
```
248+
249+
## Channel Selection Strategies
250+
251+
### All Channels (High Priority Events)
252+
```python
253+
channels=[NotificationChannel.IN_APP, NotificationChannel.PUSH, NotificationChannel.EMAIL]
254+
```
255+
Use for: Loan approvals, investment maturity, KYC approval, large payments
256+
257+
### In-App + Email (Medium Priority)
258+
```python
259+
channels=[NotificationChannel.IN_APP, NotificationChannel.EMAIL]
260+
```
261+
Use for: Wallet creation, card issuance, bill payments, transfers
262+
263+
### In-App Only (Low Priority)
264+
```python
265+
channels=[NotificationChannel.IN_APP]
266+
```
267+
Use for: Minor updates, informational messages
268+
269+
### In-App + Push (Real-time Alerts)
270+
```python
271+
channels=[NotificationChannel.IN_APP, NotificationChannel.PUSH]
272+
```
273+
Use for: Time-sensitive notifications where email delay is unacceptable
274+
275+
## Response Structure
276+
277+
```python
278+
{
279+
"success": True,
280+
"channels_attempted": ["in_app", "push", "email"],
281+
"channels_sent": ["in_app", "push", "email"],
282+
"errors": []
283+
}
284+
```
285+
286+
## User Preferences
287+
288+
The dispatcher automatically respects user preferences:
289+
290+
- `user.in_app_enabled` - Controls in-app notifications
291+
- `user.push_enabled` - Controls push notifications
292+
- `user.email_enabled` - Controls email notifications
293+
294+
If a channel is disabled for a user, it will be skipped without errors.
295+
296+
## Error Handling
297+
298+
The dispatcher handles errors gracefully:
299+
300+
```python
301+
result = UnifiedNotificationDispatcher.quick_notify(...)
302+
303+
if result["success"]:
304+
logger.info(f"Notification sent via: {result['channels_sent']}")
305+
else:
306+
logger.error(f"Notification errors: {result['errors']}")
307+
```
308+
309+
## Email Task Routing
310+
311+
The dispatcher automatically routes emails to the correct Celery task based on event type:
312+
313+
| Event Type | Email Task | Required ID Field |
314+
|------------|------------|-------------------|
315+
| KYC_APPROVED | `KYCEmailTasks.send_kyc_approved_email` | `kyc_id` |
316+
| KYC_PENDING | `KYCEmailTasks.send_kyc_pending_email` | `kyc_id` |
317+
| KYC_REJECTED | `KYCEmailTasks.send_kyc_rejected_email` | `kyc_id` |
318+
| LOAN_APPROVED | `LoanEmailTasks.send_loan_approved_email` | `loan_id` |
319+
| LOAN_DISBURSED | `LoanEmailTasks.send_loan_disbursed_email` | `loan_id` |
320+
| LOAN_REPAYMENT | `LoanEmailTasks.send_loan_repayment_email` | `repayment_id` |
321+
| INVESTMENT_CREATED | `InvestmentEmailTasks.send_investment_created_email` | `investment_id` |
322+
| INVESTMENT_MATURED | `InvestmentEmailTasks.send_investment_matured_email` | `investment_id` |
323+
| CARD_ISSUED | `CardEmailTasks.send_card_issued_email` | `card_id` |
324+
| WALLET_CREATED | `WalletEmailTasks.send_wallet_created_email` | `wallet_id` |
325+
| BILL_PAYMENT_SUCCESS | `BillPaymentEmailTasks.send_bill_payment_success_email` | `bill_payment_id` |
326+
| TRANSFER_SUCCESS | `TransferEmailTasks.send_transfer_success_email` | `transaction_id` |
327+
| PAYMENT_SUCCESS | `PaymentEmailTasks.send_payment_confirmation_email` | `payment_id` |
328+
| PAYMENT_RECEIVED | `PaymentEmailTasks.send_payment_received_email` | `payment_id` |
329+
330+
## Best Practices
331+
332+
1. **Always use event types**: Use the predefined `NotificationEventType` enum for type safety
333+
2. **Provide meaningful titles and messages**: Keep them concise and actionable
334+
3. **Include action URLs**: Provide deep links for better user experience
335+
4. **Choose appropriate priority**: Use HIGH for urgent matters, MEDIUM for standard, LOW for informational
336+
5. **Log results**: Always log the dispatch results for debugging
337+
6. **Handle errors gracefully**: Check the success flag and errors array
338+
339+
## Adding New Event Types
340+
341+
To add a new event type:
342+
343+
1. Add to `NotificationEventType` enum in `dispatcher.py`
344+
2. Add email task routing in `EMAIL_TASK_ROUTER`
345+
3. Add notification type mapping in `EVENT_TO_NOTIFICATION_TYPE`
346+
4. Update this documentation
347+
348+
## Migration from Old System
349+
350+
### Before (Old System)
351+
```python
352+
# Had to manually handle each channel
353+
NotificationService.create_notification(user, title, message, ...)
354+
PaymentEmailTasks.send_payment_email.delay(payment_id)
355+
FCMService.send_to_user(user, title, message, ...)
356+
```
357+
358+
### After (Unified System)
359+
```python
360+
# Single call handles all channels
361+
UnifiedNotificationDispatcher.quick_notify(
362+
user=user,
363+
event_type=NotificationEventType.PAYMENT_SUCCESS,
364+
title=title,
365+
message=message,
366+
object_id=str(payment_id),
367+
channels=[NotificationChannel.IN_APP, NotificationChannel.PUSH, NotificationChannel.EMAIL]
368+
)
369+
```
370+
371+
## Performance Considerations
372+
373+
- **In-app + Push**: Handled synchronously (fast)
374+
- **Email**: Dispatched to Celery queue (asynchronous, no blocking)
375+
- **User Preferences**: Checked before sending (no wasted resources)
376+
- **Error Isolation**: Failure in one channel doesn't affect others
377+
378+
## Troubleshooting
379+
380+
### Emails not sending
381+
- Check `user.email_enabled` is True
382+
- Verify Celery worker is running
383+
- Check Celery logs for task execution
384+
- Verify email task exists in routing map
385+
386+
### Push notifications not sending
387+
- Check `user.push_enabled` is True
388+
- Verify FCM configuration is correct
389+
- Check if user has FCM tokens registered
390+
391+
### In-app notifications not showing
392+
- Check `user.in_app_enabled` is True
393+
- Verify WebSocket connection is active
394+
- Check browser console for errors

0 commit comments

Comments
 (0)