django-nativemojo 0.1.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_nativemojo-0.1.10.dist-info/LICENSE +19 -0
- django_nativemojo-0.1.10.dist-info/METADATA +96 -0
- django_nativemojo-0.1.10.dist-info/NOTICE +8 -0
- django_nativemojo-0.1.10.dist-info/RECORD +194 -0
- django_nativemojo-0.1.10.dist-info/WHEEL +4 -0
- mojo/__init__.py +3 -0
- mojo/apps/account/__init__.py +1 -0
- mojo/apps/account/admin.py +91 -0
- mojo/apps/account/apps.py +16 -0
- mojo/apps/account/migrations/0001_initial.py +77 -0
- mojo/apps/account/migrations/0002_user_is_email_verified_user_is_phone_verified.py +23 -0
- mojo/apps/account/migrations/0003_group_mojo_secrets_user_mojo_secrets.py +23 -0
- mojo/apps/account/migrations/__init__.py +0 -0
- mojo/apps/account/models/__init__.py +3 -0
- mojo/apps/account/models/group.py +98 -0
- mojo/apps/account/models/member.py +95 -0
- mojo/apps/account/models/pkey.py +18 -0
- mojo/apps/account/models/user.py +211 -0
- mojo/apps/account/rest/__init__.py +3 -0
- mojo/apps/account/rest/group.py +25 -0
- mojo/apps/account/rest/user.py +47 -0
- mojo/apps/account/utils/__init__.py +0 -0
- mojo/apps/account/utils/jwtoken.py +72 -0
- mojo/apps/account/utils/passkeys.py +54 -0
- mojo/apps/fileman/README.md +549 -0
- mojo/apps/fileman/__init__.py +0 -0
- mojo/apps/fileman/apps.py +15 -0
- mojo/apps/fileman/backends/__init__.py +117 -0
- mojo/apps/fileman/backends/base.py +319 -0
- mojo/apps/fileman/backends/filesystem.py +397 -0
- mojo/apps/fileman/backends/s3.py +398 -0
- mojo/apps/fileman/examples/configurations.py +378 -0
- mojo/apps/fileman/examples/usage_example.py +665 -0
- mojo/apps/fileman/management/__init__.py +1 -0
- mojo/apps/fileman/management/commands/__init__.py +1 -0
- mojo/apps/fileman/management/commands/cleanup_expired_uploads.py +222 -0
- mojo/apps/fileman/models/__init__.py +7 -0
- mojo/apps/fileman/models/file.py +292 -0
- mojo/apps/fileman/models/manager.py +227 -0
- mojo/apps/fileman/models/render.py +0 -0
- mojo/apps/fileman/rest/__init__ +0 -0
- mojo/apps/fileman/rest/__init__.py +23 -0
- mojo/apps/fileman/rest/fileman.py +13 -0
- mojo/apps/fileman/rest/upload.py +92 -0
- mojo/apps/fileman/utils/__init__.py +19 -0
- mojo/apps/fileman/utils/upload.py +616 -0
- mojo/apps/incident/__init__.py +1 -0
- mojo/apps/incident/handlers/__init__.py +3 -0
- mojo/apps/incident/handlers/event_handlers.py +142 -0
- mojo/apps/incident/migrations/0001_initial.py +83 -0
- mojo/apps/incident/migrations/0002_rename_bundle_ruleset_bundle_minutes_event_hostname_and_more.py +44 -0
- mojo/apps/incident/migrations/0003_alter_event_model_id.py +18 -0
- mojo/apps/incident/migrations/0004_alter_incident_model_id.py +18 -0
- mojo/apps/incident/migrations/__init__.py +0 -0
- mojo/apps/incident/models/__init__.py +3 -0
- mojo/apps/incident/models/event.py +135 -0
- mojo/apps/incident/models/incident.py +33 -0
- mojo/apps/incident/models/rule.py +247 -0
- mojo/apps/incident/parsers/__init__.py +0 -0
- mojo/apps/incident/parsers/ossec/__init__.py +1 -0
- mojo/apps/incident/parsers/ossec/core.py +82 -0
- mojo/apps/incident/parsers/ossec/parsed.py +23 -0
- mojo/apps/incident/parsers/ossec/rules.py +124 -0
- mojo/apps/incident/parsers/ossec/utils.py +169 -0
- mojo/apps/incident/reporter.py +42 -0
- mojo/apps/incident/rest/__init__.py +2 -0
- mojo/apps/incident/rest/event.py +23 -0
- mojo/apps/incident/rest/ossec.py +22 -0
- mojo/apps/logit/__init__.py +0 -0
- mojo/apps/logit/admin.py +37 -0
- mojo/apps/logit/migrations/0001_initial.py +32 -0
- mojo/apps/logit/migrations/0002_log_duid_log_payload_log_username.py +28 -0
- mojo/apps/logit/migrations/0003_log_level.py +18 -0
- mojo/apps/logit/migrations/__init__.py +0 -0
- mojo/apps/logit/models/__init__.py +1 -0
- mojo/apps/logit/models/log.py +57 -0
- mojo/apps/logit/rest.py +9 -0
- mojo/apps/metrics/README.md +79 -0
- mojo/apps/metrics/__init__.py +12 -0
- mojo/apps/metrics/redis_metrics.py +331 -0
- mojo/apps/metrics/rest/__init__.py +1 -0
- mojo/apps/metrics/rest/base.py +152 -0
- mojo/apps/metrics/rest/db.py +0 -0
- mojo/apps/metrics/utils.py +227 -0
- mojo/apps/notify/README.md +91 -0
- mojo/apps/notify/README_NOTIFICATIONS.md +566 -0
- mojo/apps/notify/__init__.py +0 -0
- mojo/apps/notify/admin.py +52 -0
- mojo/apps/notify/handlers/__init__.py +0 -0
- mojo/apps/notify/handlers/example_handlers.py +516 -0
- mojo/apps/notify/handlers/ses/__init__.py +25 -0
- mojo/apps/notify/handlers/ses/bounce.py +0 -0
- mojo/apps/notify/handlers/ses/complaint.py +25 -0
- mojo/apps/notify/handlers/ses/message.py +86 -0
- mojo/apps/notify/management/__init__.py +0 -0
- mojo/apps/notify/management/commands/__init__.py +1 -0
- mojo/apps/notify/management/commands/process_notifications.py +370 -0
- mojo/apps/notify/mod +0 -0
- mojo/apps/notify/models/__init__.py +12 -0
- mojo/apps/notify/models/account.py +128 -0
- mojo/apps/notify/models/attachment.py +24 -0
- mojo/apps/notify/models/bounce.py +68 -0
- mojo/apps/notify/models/complaint.py +40 -0
- mojo/apps/notify/models/inbox.py +113 -0
- mojo/apps/notify/models/inbox_message.py +173 -0
- mojo/apps/notify/models/outbox.py +129 -0
- mojo/apps/notify/models/outbox_message.py +288 -0
- mojo/apps/notify/models/template.py +30 -0
- mojo/apps/notify/providers/__init__.py +0 -0
- mojo/apps/notify/providers/aws.py +73 -0
- mojo/apps/notify/rest/__init__.py +0 -0
- mojo/apps/notify/rest/ses.py +0 -0
- mojo/apps/notify/utils/__init__.py +2 -0
- mojo/apps/notify/utils/notifications.py +404 -0
- mojo/apps/notify/utils/parsing.py +202 -0
- mojo/apps/notify/utils/render.py +144 -0
- mojo/apps/tasks/README.md +118 -0
- mojo/apps/tasks/__init__.py +11 -0
- mojo/apps/tasks/manager.py +489 -0
- mojo/apps/tasks/rest/__init__.py +2 -0
- mojo/apps/tasks/rest/hooks.py +0 -0
- mojo/apps/tasks/rest/tasks.py +62 -0
- mojo/apps/tasks/runner.py +174 -0
- mojo/apps/tasks/tq_handlers.py +14 -0
- mojo/decorators/__init__.py +3 -0
- mojo/decorators/auth.py +25 -0
- mojo/decorators/cron.py +31 -0
- mojo/decorators/http.py +132 -0
- mojo/decorators/validate.py +14 -0
- mojo/errors.py +88 -0
- mojo/helpers/__init__.py +0 -0
- mojo/helpers/aws/__init__.py +0 -0
- mojo/helpers/aws/client.py +8 -0
- mojo/helpers/aws/s3.py +268 -0
- mojo/helpers/aws/setup_email.py +0 -0
- mojo/helpers/cron.py +79 -0
- mojo/helpers/crypto/__init__.py +4 -0
- mojo/helpers/crypto/aes.py +60 -0
- mojo/helpers/crypto/hash.py +59 -0
- mojo/helpers/crypto/privpub/__init__.py +1 -0
- mojo/helpers/crypto/privpub/hybrid.py +97 -0
- mojo/helpers/crypto/privpub/rsa.py +104 -0
- mojo/helpers/crypto/sign.py +36 -0
- mojo/helpers/crypto/too.l.py +25 -0
- mojo/helpers/crypto/utils.py +26 -0
- mojo/helpers/daemon.py +94 -0
- mojo/helpers/dates.py +69 -0
- mojo/helpers/dns/__init__.py +0 -0
- mojo/helpers/dns/godaddy.py +62 -0
- mojo/helpers/filetypes.py +128 -0
- mojo/helpers/logit.py +310 -0
- mojo/helpers/modules.py +95 -0
- mojo/helpers/paths.py +63 -0
- mojo/helpers/redis.py +10 -0
- mojo/helpers/request.py +89 -0
- mojo/helpers/request_parser.py +269 -0
- mojo/helpers/response.py +14 -0
- mojo/helpers/settings.py +146 -0
- mojo/helpers/sysinfo.py +140 -0
- mojo/helpers/ua.py +0 -0
- mojo/middleware/__init__.py +0 -0
- mojo/middleware/auth.py +26 -0
- mojo/middleware/logging.py +55 -0
- mojo/middleware/mojo.py +21 -0
- mojo/migrations/0001_initial.py +32 -0
- mojo/migrations/__init__.py +0 -0
- mojo/models/__init__.py +2 -0
- mojo/models/meta.py +262 -0
- mojo/models/rest.py +538 -0
- mojo/models/secrets.py +59 -0
- mojo/rest/__init__.py +1 -0
- mojo/rest/info.py +26 -0
- mojo/serializers/__init__.py +0 -0
- mojo/serializers/models.py +165 -0
- mojo/serializers/openapi.py +188 -0
- mojo/urls.py +38 -0
- mojo/ws4redis/README.md +174 -0
- mojo/ws4redis/__init__.py +2 -0
- mojo/ws4redis/client.py +283 -0
- mojo/ws4redis/connection.py +327 -0
- mojo/ws4redis/exceptions.py +32 -0
- mojo/ws4redis/redis.py +183 -0
- mojo/ws4redis/servers/__init__.py +0 -0
- mojo/ws4redis/servers/base.py +86 -0
- mojo/ws4redis/servers/django.py +171 -0
- mojo/ws4redis/servers/uwsgi.py +63 -0
- mojo/ws4redis/settings.py +45 -0
- mojo/ws4redis/utf8validator.py +128 -0
- mojo/ws4redis/websocket.py +403 -0
- testit/__init__.py +0 -0
- testit/client.py +147 -0
- testit/faker.py +20 -0
- testit/helpers.py +198 -0
- testit/runner.py +262 -0
@@ -0,0 +1,566 @@
|
|
1
|
+
# Django Notification System
|
2
|
+
|
3
|
+
A comprehensive multi-channel notification system for Django that supports email, SMS, WhatsApp, push notifications, and more.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
This notification system provides a unified approach to handling both incoming and outgoing messages across multiple communication channels. It's designed to be flexible, scalable, and easy to integrate with existing Django applications.
|
8
|
+
|
9
|
+
## Architecture
|
10
|
+
|
11
|
+
The system consists of several key components:
|
12
|
+
|
13
|
+
- **Account**: Represents a notification service account (email domain, SMS provider, etc.)
|
14
|
+
- **Inbox**: Receives messages from external sources
|
15
|
+
- **InboxMessage**: Stores received messages
|
16
|
+
- **Outbox**: Sends messages to external recipients
|
17
|
+
- **OutboxMessage**: Stores outgoing messages with status tracking
|
18
|
+
|
19
|
+
## Models
|
20
|
+
|
21
|
+
### Account
|
22
|
+
|
23
|
+
Represents a notification service account that can send/receive messages.
|
24
|
+
|
25
|
+
```python
|
26
|
+
from mojo.apps.notify.models import Account
|
27
|
+
|
28
|
+
# Create an email account
|
29
|
+
email_account = Account.objects.create(
|
30
|
+
kind=Account.EMAIL,
|
31
|
+
domain='example.com',
|
32
|
+
group=my_group,
|
33
|
+
settings={
|
34
|
+
'smtp': {
|
35
|
+
'host': 'smtp.example.com',
|
36
|
+
'port': 587,
|
37
|
+
'username': 'user@example.com',
|
38
|
+
'password': 'password',
|
39
|
+
'use_tls': True
|
40
|
+
}
|
41
|
+
}
|
42
|
+
)
|
43
|
+
|
44
|
+
# Create an SMS account
|
45
|
+
sms_account = Account.objects.create(
|
46
|
+
kind=Account.SMS,
|
47
|
+
domain='+1234567890',
|
48
|
+
group=my_group,
|
49
|
+
settings={
|
50
|
+
'sms_provider': {
|
51
|
+
'provider': 'twilio',
|
52
|
+
'account_sid': 'your_sid',
|
53
|
+
'auth_token': 'your_token'
|
54
|
+
}
|
55
|
+
}
|
56
|
+
)
|
57
|
+
```
|
58
|
+
|
59
|
+
### Inbox
|
60
|
+
|
61
|
+
Defines an inbox for receiving messages.
|
62
|
+
|
63
|
+
```python
|
64
|
+
from mojo.apps.notify.models import Inbox
|
65
|
+
|
66
|
+
# Create an email inbox
|
67
|
+
email_inbox = Inbox.objects.create(
|
68
|
+
account=email_account,
|
69
|
+
address='support@example.com',
|
70
|
+
sync_handler='myapp.handlers.on_email_received',
|
71
|
+
async_handler='myapp.handlers.on_email_received_async'
|
72
|
+
)
|
73
|
+
|
74
|
+
# Create an SMS inbox
|
75
|
+
sms_inbox = Inbox.objects.create(
|
76
|
+
account=sms_account,
|
77
|
+
address='+1234567890',
|
78
|
+
sync_handler='myapp.handlers.on_sms_received'
|
79
|
+
)
|
80
|
+
```
|
81
|
+
|
82
|
+
### Outbox
|
83
|
+
|
84
|
+
Defines an outbox for sending messages.
|
85
|
+
|
86
|
+
```python
|
87
|
+
from mojo.apps.notify.models import Outbox
|
88
|
+
|
89
|
+
# Create an email outbox
|
90
|
+
email_outbox = Outbox.objects.create(
|
91
|
+
account=email_account,
|
92
|
+
group=my_group,
|
93
|
+
address='noreply@example.com',
|
94
|
+
handler='myapp.handlers.on_email_send',
|
95
|
+
rate_limit=1000 # Max 1000 emails per hour
|
96
|
+
)
|
97
|
+
|
98
|
+
# Create an SMS outbox
|
99
|
+
sms_outbox = Outbox.objects.create(
|
100
|
+
account=sms_account,
|
101
|
+
group=my_group,
|
102
|
+
address='+1234567890',
|
103
|
+
handler='myapp.handlers.on_sms_send',
|
104
|
+
rate_limit=100 # Max 100 SMS per hour
|
105
|
+
)
|
106
|
+
```
|
107
|
+
|
108
|
+
## Usage Examples
|
109
|
+
|
110
|
+
### Sending Messages
|
111
|
+
|
112
|
+
```python
|
113
|
+
from mojo.apps.notify.utils.notifications import send_email, send_sms, send_whatsapp, send_push
|
114
|
+
|
115
|
+
# Send an email
|
116
|
+
email_message = send_email(
|
117
|
+
to_address='user@example.com',
|
118
|
+
subject='Welcome!',
|
119
|
+
message='Welcome to our platform!',
|
120
|
+
from_address='welcome@example.com',
|
121
|
+
user=user_instance,
|
122
|
+
metadata={'campaign': 'welcome_series'}
|
123
|
+
)
|
124
|
+
|
125
|
+
# Send an SMS
|
126
|
+
sms_message = send_sms(
|
127
|
+
to_address='+1987654321',
|
128
|
+
message='Your verification code is: 123456',
|
129
|
+
user=user_instance
|
130
|
+
)
|
131
|
+
|
132
|
+
# Send a WhatsApp message
|
133
|
+
whatsapp_message = send_whatsapp(
|
134
|
+
to_address='+1987654321',
|
135
|
+
message='Hello from WhatsApp!',
|
136
|
+
metadata={'template_id': 'greeting'}
|
137
|
+
)
|
138
|
+
|
139
|
+
# Send a push notification
|
140
|
+
push_message = send_push(
|
141
|
+
to_address='device_token_here',
|
142
|
+
title='New Message',
|
143
|
+
message='You have a new message!',
|
144
|
+
user=user_instance,
|
145
|
+
metadata={'badge_count': 5}
|
146
|
+
)
|
147
|
+
|
148
|
+
# Schedule a message for later
|
149
|
+
from django.utils import timezone
|
150
|
+
from datetime import timedelta
|
151
|
+
|
152
|
+
scheduled_message = send_email(
|
153
|
+
to_address='user@example.com',
|
154
|
+
subject='Reminder',
|
155
|
+
message='Don\'t forget about your appointment tomorrow!',
|
156
|
+
scheduled_at=timezone.now() + timedelta(hours=24)
|
157
|
+
)
|
158
|
+
```
|
159
|
+
|
160
|
+
### Bulk Messaging
|
161
|
+
|
162
|
+
```python
|
163
|
+
from mojo.apps.notify.utils.notifications import BulkNotifier
|
164
|
+
|
165
|
+
# Send bulk emails
|
166
|
+
recipients = ['user1@example.com', 'user2@example.com', 'user3@example.com']
|
167
|
+
messages = BulkNotifier.send_bulk_email(
|
168
|
+
recipients=recipients,
|
169
|
+
subject='Newsletter',
|
170
|
+
message='Check out our latest updates!',
|
171
|
+
batch_size=50
|
172
|
+
)
|
173
|
+
|
174
|
+
# Send bulk SMS
|
175
|
+
phone_numbers = ['+1111111111', '+2222222222', '+3333333333']
|
176
|
+
sms_messages = BulkNotifier.send_bulk_sms(
|
177
|
+
recipients=phone_numbers,
|
178
|
+
message='Flash sale - 50% off everything!',
|
179
|
+
batch_size=25
|
180
|
+
)
|
181
|
+
```
|
182
|
+
|
183
|
+
### Processing Received Messages
|
184
|
+
|
185
|
+
Messages are automatically processed when received if handlers are configured. You can also manually process them:
|
186
|
+
|
187
|
+
```python
|
188
|
+
from mojo.apps.notify.utils.notifications import MessageProcessor
|
189
|
+
|
190
|
+
# Process a single message
|
191
|
+
success = MessageProcessor.process_inbox_message(inbox_message)
|
192
|
+
|
193
|
+
# Bulk process messages for an inbox
|
194
|
+
processed_count = MessageProcessor.bulk_process_inbox_messages(
|
195
|
+
inbox=my_inbox,
|
196
|
+
limit=100
|
197
|
+
)
|
198
|
+
```
|
199
|
+
|
200
|
+
## Message Handlers
|
201
|
+
|
202
|
+
### Creating Handlers
|
203
|
+
|
204
|
+
Handlers are Python functions that process incoming or outgoing messages. Here are examples:
|
205
|
+
|
206
|
+
#### Inbox Handler (Receiving Messages)
|
207
|
+
|
208
|
+
```python
|
209
|
+
# myapp/handlers.py
|
210
|
+
import logging
|
211
|
+
from mojo.apps.notify.models import InboxMessage
|
212
|
+
|
213
|
+
logger = logging.getLogger(__name__)
|
214
|
+
|
215
|
+
def on_email_received(inbox_message: InboxMessage) -> bool:
|
216
|
+
"""Handle received email messages"""
|
217
|
+
try:
|
218
|
+
# Extract message details
|
219
|
+
sender = inbox_message.from_address
|
220
|
+
subject = inbox_message.subject
|
221
|
+
message = inbox_message.message
|
222
|
+
|
223
|
+
# Process the message
|
224
|
+
if 'support' in inbox_message.to_address:
|
225
|
+
# Create support ticket
|
226
|
+
create_support_ticket(sender, subject, message)
|
227
|
+
elif 'billing' in inbox_message.to_address:
|
228
|
+
# Handle billing inquiry
|
229
|
+
handle_billing_inquiry(inbox_message)
|
230
|
+
|
231
|
+
# Send auto-reply if needed
|
232
|
+
if should_send_auto_reply(inbox_message):
|
233
|
+
send_auto_reply(inbox_message)
|
234
|
+
|
235
|
+
logger.info(f"Processed email from {sender}")
|
236
|
+
return True
|
237
|
+
|
238
|
+
except Exception as e:
|
239
|
+
logger.error(f"Error processing email: {e}")
|
240
|
+
return False
|
241
|
+
|
242
|
+
async def on_email_received_async(inbox_message: InboxMessage) -> bool:
|
243
|
+
"""Async version for high-volume processing"""
|
244
|
+
# Implement async processing logic
|
245
|
+
return True
|
246
|
+
```
|
247
|
+
|
248
|
+
#### Outbox Handler (Sending Messages)
|
249
|
+
|
250
|
+
```python
|
251
|
+
def on_email_send(outbox_message: OutboxMessage) -> bool:
|
252
|
+
"""Handle sending email messages"""
|
253
|
+
try:
|
254
|
+
# Mark as sending
|
255
|
+
outbox_message.mark_sending()
|
256
|
+
|
257
|
+
# Get email settings
|
258
|
+
account = outbox_message.account
|
259
|
+
smtp_settings = account.get_setting('smtp', {})
|
260
|
+
|
261
|
+
# Send the email
|
262
|
+
send_via_smtp(outbox_message, smtp_settings)
|
263
|
+
|
264
|
+
# Mark as sent
|
265
|
+
outbox_message.mark_sent(message_id='external_message_id')
|
266
|
+
return True
|
267
|
+
|
268
|
+
except Exception as e:
|
269
|
+
# Mark as failed
|
270
|
+
outbox_message.mark_failed(str(e))
|
271
|
+
return False
|
272
|
+
```
|
273
|
+
|
274
|
+
### Handler Configuration
|
275
|
+
|
276
|
+
Configure handlers in your models:
|
277
|
+
|
278
|
+
```python
|
279
|
+
# In your inbox
|
280
|
+
inbox.sync_handler = 'myapp.handlers.on_email_received'
|
281
|
+
inbox.async_handler = 'myapp.handlers.on_email_received_async'
|
282
|
+
|
283
|
+
# In your outbox
|
284
|
+
outbox.handler = 'myapp.handlers.on_email_send'
|
285
|
+
```
|
286
|
+
|
287
|
+
## Management Commands
|
288
|
+
|
289
|
+
### Process Notifications
|
290
|
+
|
291
|
+
Use the management command to process messages:
|
292
|
+
|
293
|
+
```bash
|
294
|
+
# Process all pending messages once
|
295
|
+
python manage.py process_notifications
|
296
|
+
|
297
|
+
# Run as daemon (continuous processing)
|
298
|
+
python manage.py process_notifications --daemon --interval=30
|
299
|
+
|
300
|
+
# Process only inbox messages
|
301
|
+
python manage.py process_notifications --inbox-only
|
302
|
+
|
303
|
+
# Process only outbox messages
|
304
|
+
python manage.py process_notifications --outbox-only
|
305
|
+
|
306
|
+
# Process only email messages
|
307
|
+
python manage.py process_notifications --kind=email
|
308
|
+
|
309
|
+
# Retry failed messages
|
310
|
+
python manage.py process_notifications --retry-failed --max-age-hours=12
|
311
|
+
|
312
|
+
# Limit batch size
|
313
|
+
python manage.py process_notifications --limit=50
|
314
|
+
```
|
315
|
+
|
316
|
+
### Production Deployment
|
317
|
+
|
318
|
+
For production, run the processor as a daemon:
|
319
|
+
|
320
|
+
```bash
|
321
|
+
# Using systemd or supervisor
|
322
|
+
python manage.py process_notifications --daemon --interval=10
|
323
|
+
```
|
324
|
+
|
325
|
+
## Message Status Tracking
|
326
|
+
|
327
|
+
### Outbox Message Status
|
328
|
+
|
329
|
+
- `PENDING`: Message is queued for sending
|
330
|
+
- `SENDING`: Message is currently being sent
|
331
|
+
- `SENT`: Message was successfully sent
|
332
|
+
- `FAILED`: Message failed to send
|
333
|
+
- `CANCELLED`: Message was cancelled
|
334
|
+
|
335
|
+
### Status Checking
|
336
|
+
|
337
|
+
```python
|
338
|
+
# Check message status
|
339
|
+
if outbox_message.is_sent:
|
340
|
+
print("Message delivered successfully")
|
341
|
+
elif outbox_message.is_failed:
|
342
|
+
print(f"Message failed: {outbox_message.error_message}")
|
343
|
+
if outbox_message.can_retry:
|
344
|
+
print("Message can be retried")
|
345
|
+
|
346
|
+
# Retry failed messages
|
347
|
+
if outbox_message.can_retry:
|
348
|
+
outbox_message.reset_for_retry()
|
349
|
+
```
|
350
|
+
|
351
|
+
## Rate Limiting
|
352
|
+
|
353
|
+
Configure rate limits on outboxes:
|
354
|
+
|
355
|
+
```python
|
356
|
+
# Limit to 1000 messages per hour
|
357
|
+
outbox.rate_limit = 1000
|
358
|
+
outbox.save()
|
359
|
+
|
360
|
+
# Check rate limit
|
361
|
+
if outbox.check_rate_limit():
|
362
|
+
# Safe to send
|
363
|
+
pass
|
364
|
+
else:
|
365
|
+
# Rate limit exceeded
|
366
|
+
pass
|
367
|
+
```
|
368
|
+
|
369
|
+
## Message Metadata
|
370
|
+
|
371
|
+
Use metadata to store additional information:
|
372
|
+
|
373
|
+
```python
|
374
|
+
# Email with attachments info
|
375
|
+
email_message = send_email(
|
376
|
+
to_address='user@example.com',
|
377
|
+
subject='Invoice',
|
378
|
+
message='Please find your invoice attached.',
|
379
|
+
metadata={
|
380
|
+
'attachments': ['invoice.pdf'],
|
381
|
+
'category': 'billing',
|
382
|
+
'priority': 'high'
|
383
|
+
}
|
384
|
+
)
|
385
|
+
|
386
|
+
# SMS with delivery tracking
|
387
|
+
sms_message = send_sms(
|
388
|
+
to_address='+1234567890',
|
389
|
+
message='Your order has shipped!',
|
390
|
+
metadata={
|
391
|
+
'order_id': '12345',
|
392
|
+
'tracking_url': 'https://track.example.com/12345'
|
393
|
+
}
|
394
|
+
)
|
395
|
+
|
396
|
+
# Access metadata
|
397
|
+
attachment_info = email_message.get_metadata_value('attachments')
|
398
|
+
```
|
399
|
+
|
400
|
+
## Statistics and Monitoring
|
401
|
+
|
402
|
+
```python
|
403
|
+
from mojo.apps.notify.utils.notifications import MessageStats
|
404
|
+
|
405
|
+
# Get outbox statistics
|
406
|
+
stats = MessageStats.get_outbox_stats(my_outbox)
|
407
|
+
print(f"Total sent: {stats['sent_messages']}")
|
408
|
+
print(f"Failed: {stats['failed_messages']}")
|
409
|
+
print(f"Recent (24h): {stats['recent_messages']}")
|
410
|
+
|
411
|
+
# Get inbox statistics
|
412
|
+
inbox_stats = MessageStats.get_inbox_stats(my_inbox)
|
413
|
+
print(f"Unprocessed: {inbox_stats['unprocessed_messages']}")
|
414
|
+
```
|
415
|
+
|
416
|
+
## Integration Examples
|
417
|
+
|
418
|
+
### With Django Signals
|
419
|
+
|
420
|
+
```python
|
421
|
+
from django.db.models.signals import post_save
|
422
|
+
from django.dispatch import receiver
|
423
|
+
from django.contrib.auth.models import User
|
424
|
+
from mojo.apps.notify.utils.notifications import send_email
|
425
|
+
|
426
|
+
@receiver(post_save, sender=User)
|
427
|
+
def send_welcome_email(sender, instance, created, **kwargs):
|
428
|
+
if created:
|
429
|
+
send_email(
|
430
|
+
to_address=instance.email,
|
431
|
+
subject='Welcome!',
|
432
|
+
message=f'Welcome to our platform, {instance.first_name}!',
|
433
|
+
user=instance,
|
434
|
+
metadata={'trigger': 'user_registration'}
|
435
|
+
)
|
436
|
+
```
|
437
|
+
|
438
|
+
### With Celery (Async)
|
439
|
+
|
440
|
+
```python
|
441
|
+
from celery import shared_task
|
442
|
+
from mojo.apps.notify.utils.notifications import send_email
|
443
|
+
|
444
|
+
@shared_task
|
445
|
+
def send_notification_email(user_id, subject, message):
|
446
|
+
from django.contrib.auth.models import User
|
447
|
+
user = User.objects.get(id=user_id)
|
448
|
+
|
449
|
+
return send_email(
|
450
|
+
to_address=user.email,
|
451
|
+
subject=subject,
|
452
|
+
message=message,
|
453
|
+
user=user
|
454
|
+
)
|
455
|
+
```
|
456
|
+
|
457
|
+
### With REST API
|
458
|
+
|
459
|
+
```python
|
460
|
+
# views.py
|
461
|
+
from rest_framework.decorators import api_view
|
462
|
+
from rest_framework.response import Response
|
463
|
+
from mojo.apps.notify.utils.notifications import send_sms
|
464
|
+
|
465
|
+
@api_view(['POST'])
|
466
|
+
def send_sms_notification(request):
|
467
|
+
phone = request.data.get('phone')
|
468
|
+
message = request.data.get('message')
|
469
|
+
|
470
|
+
sms_message = send_sms(
|
471
|
+
to_address=phone,
|
472
|
+
message=message,
|
473
|
+
user=request.user
|
474
|
+
)
|
475
|
+
|
476
|
+
return Response({
|
477
|
+
'message_id': sms_message.id,
|
478
|
+
'status': sms_message.status
|
479
|
+
})
|
480
|
+
```
|
481
|
+
|
482
|
+
## Configuration
|
483
|
+
|
484
|
+
### Settings
|
485
|
+
|
486
|
+
Add to your Django settings:
|
487
|
+
|
488
|
+
```python
|
489
|
+
# settings.py
|
490
|
+
|
491
|
+
# Notification system settings
|
492
|
+
NOTIFICATION_SETTINGS = {
|
493
|
+
'DEFAULT_FROM_EMAIL': 'noreply@example.com',
|
494
|
+
'DEFAULT_SMS_FROM': '+1234567890',
|
495
|
+
'RATE_LIMIT_WINDOW': 3600, # 1 hour in seconds
|
496
|
+
'MAX_RETRY_ATTEMPTS': 3,
|
497
|
+
'PROCESSING_BATCH_SIZE': 100,
|
498
|
+
}
|
499
|
+
|
500
|
+
# Add to installed apps
|
501
|
+
INSTALLED_APPS = [
|
502
|
+
# ... other apps
|
503
|
+
'mojo.apps.notify',
|
504
|
+
]
|
505
|
+
```
|
506
|
+
|
507
|
+
### Database Migration
|
508
|
+
|
509
|
+
Run migrations to create the notification tables:
|
510
|
+
|
511
|
+
```bash
|
512
|
+
python manage.py makemigrations notify
|
513
|
+
python manage.py migrate notify
|
514
|
+
```
|
515
|
+
|
516
|
+
## Admin Interface
|
517
|
+
|
518
|
+
The system includes Django admin integration for managing accounts, inboxes, outboxes, and messages. Access it at `/admin/notify/`.
|
519
|
+
|
520
|
+
## Security Considerations
|
521
|
+
|
522
|
+
1. **API Keys**: Store sensitive settings like API keys in environment variables
|
523
|
+
2. **Rate Limiting**: Configure appropriate rate limits to prevent abuse
|
524
|
+
3. **Validation**: Validate all input addresses and content
|
525
|
+
4. **Permissions**: Use Django's permission system to control access
|
526
|
+
5. **Logging**: Log all message activities for audit trails
|
527
|
+
|
528
|
+
## Troubleshooting
|
529
|
+
|
530
|
+
### Common Issues
|
531
|
+
|
532
|
+
1. **Handler Import Errors**: Ensure handler functions are properly importable
|
533
|
+
2. **Rate Limit Exceeded**: Check outbox rate limits and adjust as needed
|
534
|
+
3. **Failed Messages**: Check error messages in OutboxMessage.error_message
|
535
|
+
4. **Missing Settings**: Verify account settings are properly configured
|
536
|
+
|
537
|
+
### Debugging
|
538
|
+
|
539
|
+
```python
|
540
|
+
# Enable debug logging
|
541
|
+
import logging
|
542
|
+
logging.getLogger('mojo.apps.notify').setLevel(logging.DEBUG)
|
543
|
+
|
544
|
+
# Check message status
|
545
|
+
message = OutboxMessage.objects.get(id=123)
|
546
|
+
print(f"Status: {message.status}")
|
547
|
+
print(f"Error: {message.error_message}")
|
548
|
+
print(f"Retry count: {message.retry_count}")
|
549
|
+
```
|
550
|
+
|
551
|
+
## Performance Tips
|
552
|
+
|
553
|
+
1. Use async handlers for high-volume processing
|
554
|
+
2. Configure appropriate batch sizes
|
555
|
+
3. Monitor rate limits and adjust as needed
|
556
|
+
4. Use database indexes on frequently queried fields
|
557
|
+
5. Consider using Celery for background processing
|
558
|
+
6. Implement message archiving for old messages
|
559
|
+
|
560
|
+
## Support
|
561
|
+
|
562
|
+
For issues and questions:
|
563
|
+
1. Check the logs for error messages
|
564
|
+
2. Verify handler configuration
|
565
|
+
3. Test with small batches first
|
566
|
+
4. Monitor rate limits and quotas
|
File without changes
|
@@ -0,0 +1,52 @@
|
|
1
|
+
from django.contrib import admin
|
2
|
+
from . import models
|
3
|
+
|
4
|
+
|
5
|
+
@admin.register(models.Account)
|
6
|
+
class AccountAdmin(admin.ModelAdmin):
|
7
|
+
list_display = ['kind', 'domain', 'group', 'is_active', 'created']
|
8
|
+
list_filter = ['kind', 'is_active', 'created']
|
9
|
+
search_fields = ['domain', 'group__name']
|
10
|
+
ordering = ['-created']
|
11
|
+
|
12
|
+
|
13
|
+
@admin.register(models.Inbox)
|
14
|
+
class InboxAdmin(admin.ModelAdmin):
|
15
|
+
list_display = ['address', 'account', 'is_active', 'created']
|
16
|
+
list_filter = ['account__kind', 'is_active', 'created']
|
17
|
+
search_fields = ['address', 'account__domain']
|
18
|
+
ordering = ['-created']
|
19
|
+
|
20
|
+
|
21
|
+
@admin.register(models.InboxMessage)
|
22
|
+
class InboxMessageAdmin(admin.ModelAdmin):
|
23
|
+
list_display = ['from_address', 'to_address', 'subject', 'inbox', 'processed', 'created']
|
24
|
+
list_filter = ['processed', 'inbox__account__kind', 'created']
|
25
|
+
search_fields = ['from_address', 'to_address', 'subject', 'message']
|
26
|
+
ordering = ['-created']
|
27
|
+
readonly_fields = ['created', 'modified']
|
28
|
+
|
29
|
+
|
30
|
+
@admin.register(models.Outbox)
|
31
|
+
class OutboxAdmin(admin.ModelAdmin):
|
32
|
+
list_display = ['address', 'account', 'group', 'is_active', 'rate_limit', 'created']
|
33
|
+
list_filter = ['account__kind', 'is_active', 'created']
|
34
|
+
search_fields = ['address', 'account__domain', 'group__name']
|
35
|
+
ordering = ['-created']
|
36
|
+
|
37
|
+
|
38
|
+
@admin.register(models.OutboxMessage)
|
39
|
+
class OutboxMessageAdmin(admin.ModelAdmin):
|
40
|
+
list_display = ['from_address', 'to_address', 'subject', 'status', 'outbox', 'created']
|
41
|
+
list_filter = ['status', 'outbox__account__kind', 'created']
|
42
|
+
search_fields = ['from_address', 'to_address', 'subject', 'message']
|
43
|
+
ordering = ['-created']
|
44
|
+
readonly_fields = ['created', 'modified', 'sent_at', 'failed_at']
|
45
|
+
|
46
|
+
|
47
|
+
# Register existing models
|
48
|
+
admin.site.register(models.Message)
|
49
|
+
admin.site.register(models.Attachment)
|
50
|
+
admin.site.register(models.Bounce)
|
51
|
+
admin.site.register(models.Complaint)
|
52
|
+
admin.site.register(models.NotifyTemplate)
|
File without changes
|