Skip to content

Commit 650b22f

Browse files
committed
commit 1234567890abcdef1234567890abcdef12345678
1 parent 21c04fe commit 650b22f

105 files changed

Lines changed: 891 additions & 7180 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backend/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ RUN pip install -r requirements.txt
2424
COPY . /app/
2525

2626
# Create necessary directories
27-
RUN mkdir -p /app/staticfiles /app/media
27+
RUN mkdir -p /app/staticfiles /app/media /app/sessions
2828

2929
# Collect static files
3030
RUN python manage.py collectstatic --noinput || true

backend/config/settings.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,10 @@
7171
'community',
7272
'activity',
7373
# 'chatbot_service', # directory removed — re-add when restored
74-
'research_lab',
74+
# 'research_lab', # directory removed — re-add when restored
7575
# 'ai_lab', # directory removed — re-add when restored
76-
'iot_lab',
77-
'self_lab',
76+
# 'iot_lab', # directory removed — re-add when restored
77+
# 'self_lab', # directory removed — re-add when restored
7878
'developer_settings',
7979
'model_flow',
8080
'emails',
@@ -430,6 +430,9 @@
430430
EMAIL_REPLY_TO = config('EMAIL_REPLY_TO', default='support@atonixdev.com')
431431
SERVER_EMAIL = config('SERVER_EMAIL', default='errors@atonixdev.com')
432432

433+
# Inbound email webhook shared secret (validated against X-Webhook-Token header)
434+
INBOUND_WEBHOOK_SECRET = config('INBOUND_WEBHOOK_SECRET', default='')
435+
433436
# Public URL used in email CTAs (no trailing slash)
434437
FRONTEND_URL = config('FRONTEND_URL', default='https://atonixdev.com').rstrip('/')
435438

backend/config/urls.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@
3737
path('api/community/', include('community.urls')),
3838
path('api/', include('activity.urls')),
3939
# path('api/chatbot/', include('chatbot_service.urls')), # module removed
40-
path('api/research-lab/', include('research_lab.urls')),
40+
# path('api/research-lab/', include('research_lab.urls')), # module removed
4141
# path('api/ai-lab/', include('ai_lab.urls')), # module removed
42-
path('api/iot-lab/', include('iot_lab.urls')),
43-
path('api/self-lab/', include('self_lab.urls')),
42+
# path('api/iot-lab/', include('iot_lab.urls')), # module removed
43+
# path('api/self-lab/', include('self_lab.urls')), # module removed
4444
path('api/v1/settings/', include('developer_settings.urls')),
4545
path('api/v1/flow/', include('model_flow.urls')),
4646
path('api/admin/', include('emails.urls')),
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Generated by Django 4.2.7 on 2026-03-10 11:07
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('emails', '0002_campaign_senderidentity_emailtemplate_campaignlog_and_more'),
10+
]
11+
12+
operations = [
13+
migrations.CreateModel(
14+
name='InboundEmail',
15+
fields=[
16+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
17+
('message_id', models.CharField(blank=True, db_index=True, default='', max_length=512)),
18+
('from_email', models.EmailField(max_length=254)),
19+
('from_name', models.CharField(blank=True, default='', max_length=255)),
20+
('to_email', models.CharField(max_length=512)),
21+
('cc', models.TextField(blank=True, default='')),
22+
('reply_to', models.EmailField(blank=True, default='', max_length=254)),
23+
('in_reply_to', models.CharField(blank=True, default='', max_length=512)),
24+
('subject', models.CharField(blank=True, default='(no subject)', max_length=998)),
25+
('html_body', models.TextField(blank=True, default='')),
26+
('text_body', models.TextField(blank=True, default='')),
27+
('preview_text', models.CharField(blank=True, default='', max_length=255)),
28+
('status', models.CharField(choices=[('unread', 'Unread'), ('read', 'Read'), ('archived', 'Archived'), ('spam', 'Spam')], default='unread', max_length=16)),
29+
('has_attachments', models.BooleanField(default=False)),
30+
('attachments', models.JSONField(blank=True, default=list)),
31+
('headers', models.JSONField(blank=True, default=dict)),
32+
('raw_payload', models.JSONField(blank=True, default=dict, help_text='Full raw webhook payload for debugging')),
33+
('spam_score', models.FloatField(blank=True, null=True)),
34+
('received_at', models.DateTimeField(blank=True, help_text='Date header from the email itself', null=True)),
35+
('created_at', models.DateTimeField(auto_now_add=True)),
36+
],
37+
options={
38+
'verbose_name': 'Inbound Email',
39+
'verbose_name_plural': 'Inbound Emails',
40+
'ordering': ['-created_at'],
41+
'indexes': [models.Index(fields=['status', 'created_at'], name='emails_inbo_status_ee4e51_idx'), models.Index(fields=['from_email', 'created_at'], name='emails_inbo_from_em_9ae48c_idx'), models.Index(fields=['message_id'], name='emails_inbo_message_5ff5ef_idx')],
42+
},
43+
),
44+
]

backend/emails/models.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,3 +352,60 @@ class Meta:
352352
def __str__(self):
353353
return f"[{self.campaign.name}] → {self.recipient} ({self.status})"
354354

355+
356+
# ── Inbound Email Inbox ───────────────────────────────────────────────────────
357+
358+
class InboundEmail(models.Model):
359+
"""
360+
Received email stored via the inbound webhook.
361+
Compatible with Brevo Inbound Parsing and similar services
362+
(Mailgun inbound, SendGrid Inbound Parse, etc.).
363+
"""
364+
365+
STATUS_CHOICES = [
366+
('unread', 'Unread'),
367+
('read', 'Read'),
368+
('archived', 'Archived'),
369+
('spam', 'Spam'),
370+
]
371+
372+
# Envelope
373+
message_id = models.CharField(max_length=512, blank=True, default='', db_index=True)
374+
from_email = models.EmailField()
375+
from_name = models.CharField(max_length=255, blank=True, default='')
376+
to_email = models.CharField(max_length=512) # may be comma-separated
377+
cc = models.TextField(blank=True, default='')
378+
reply_to = models.EmailField(blank=True, default='')
379+
in_reply_to = models.CharField(max_length=512, blank=True, default='')
380+
381+
# Content
382+
subject = models.CharField(max_length=998, blank=True, default='(no subject)')
383+
html_body = models.TextField(blank=True, default='')
384+
text_body = models.TextField(blank=True, default='')
385+
preview_text = models.CharField(max_length=255, blank=True, default='')
386+
387+
# Meta
388+
status = models.CharField(max_length=16, choices=STATUS_CHOICES, default='unread')
389+
has_attachments = models.BooleanField(default=False)
390+
attachments = models.JSONField(default=list, blank=True)
391+
headers = models.JSONField(default=dict, blank=True)
392+
raw_payload = models.JSONField(default=dict, blank=True,
393+
help_text='Full raw webhook payload for debugging')
394+
spam_score = models.FloatField(null=True, blank=True)
395+
received_at = models.DateTimeField(null=True, blank=True,
396+
help_text='Date header from the email itself')
397+
created_at = models.DateTimeField(auto_now_add=True)
398+
399+
class Meta:
400+
ordering = ['-created_at']
401+
indexes = [
402+
models.Index(fields=['status', 'created_at']),
403+
models.Index(fields=['from_email', 'created_at']),
404+
models.Index(fields=['message_id']),
405+
]
406+
verbose_name = 'Inbound Email'
407+
verbose_name_plural = 'Inbound Emails'
408+
409+
def __str__(self):
410+
return f"[{self.status}] From {self.from_email}: {self.subject}"
411+

backend/emails/urls.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
EmailTemplateListView, EmailTemplateDetailView, EmailTemplatePreviewView,
55
SenderIdentityListView, SenderIdentityDetailView,
66
CampaignListView, CampaignDetailView, CampaignSendView, CampaignLogListView,
7+
InboundEmailWebhookView, InboundEmailListView, InboundEmailDetailView,
78
)
89

910
urlpatterns = [
@@ -22,4 +23,8 @@
2223
path('campaigns/<int:pk>/', CampaignDetailView.as_view(), name='admin-campaign-detail'),
2324
path('campaigns/<int:pk>/send/', CampaignSendView.as_view(), name='admin-campaign-send'),
2425
path('campaigns/<int:pk>/logs/', CampaignLogListView.as_view(), name='admin-campaign-logs'),
26+
# Inbound email inbox
27+
path('inbound/webhook/', InboundEmailWebhookView.as_view(), name='inbound-email-webhook'),
28+
path('inbound/', InboundEmailListView.as_view(), name='inbound-email-list'),
29+
path('inbound/<int:pk>/', InboundEmailDetailView.as_view(), name='inbound-email-detail'),
2530
]

0 commit comments

Comments
 (0)