The communication module provides a robust, multi-tenant, enterprise-grade email and messaging system for the ERP platform. It supports:
- Internal and external email communication
- Multi-tenant data isolation
- Fine-grained permission controls (Super Admin, Tenant Admin, Employee)
- Folder management (Inbox, Sent, Drafts, Trash, Archive, Spam, Custom)
- Email templates and signatures
- Attachments and recipient tracking
- RESTful API endpoints for all operations
This module is designed for scalability, security, and seamless integration with the ERP's client/tenant architecture.
- Purpose: Organizes emails into system and custom folders per user.
- Fields:
id: UUID, primary keyname: Folder name (e.g., Inbox, Sent)folder_type: Enum (inbox, sent, drafts, trash, archive, spam, custom)owner: FK to user (folder owner)parent_folder: FK to self (for nested folders)is_system_folder: Boolean (system vs. user-created)created_at,updated_at: Timestamps
- Relationships: Each folder belongs to a user; can have subfolders.
- Rationale: System folders are auto-created for each user; custom folders allow user-defined organization.
- Purpose: Stores email messages and metadata.
- Fields:
id: UUID, primary keysubject,from_email,to_emails,cc_emails,bcc_emails,reply_to: Email headerscontent_text,content_html: Email bodysender: FK to user (who sent the email)status: Enum (draft, sent, delivered, failed, pending)priority: Enum (low, normal, high, urgent)is_read,is_important,is_starred: Flagsthread_id,in_reply_to: Threading supportscheduled_at,sent_at,delivered_at,read_at: Timestampsmessage_id,raw_email: External email supportcreated_at,updated_at: Timestamps
- Relationships:
- Linked to
EmailRecipient(recipients) - Linked to
EmailAttachment(attachments) - Linked to
EmailUserFolder(user-folder mapping)
- Linked to
- Rationale: Supports all standard email features, threading, and multi-user/tenant context.
- Purpose: Tracks each recipient's status for an email.
- Fields:
id: UUID, primary keyemail: FK to Emailrecipient_email: Email addressrecipient_user: FK to user (if internal)recipient_type: Enum (to, cc, bcc)status: Enum (pending, sent, delivered, read, failed, bounced)sent_at,delivered_at,read_at,bounced_at: Timestampserror_message: Error detailscreated_at,updated_at: Timestamps
- Rationale: Enables per-recipient delivery/read tracking and error handling.
- Purpose: Stores file attachments for emails.
- Fields:
id,filename,content_type,file_size,is_inline,content_id,file,created_at
- Rationale: Supports both inline and regular attachments.
- Purpose: Maps emails to folders for each user (multi-folder support).
- Fields:
id,email(FK),user(FK),folder(FK),is_starred,is_read,moved_at,created_at
- Rationale: Allows users to organize emails independently (e.g., move to custom folders).
- Purpose: Stores reusable email templates.
- Fields:
id,name,subject_template,content_html,template_type(system, tenant, user),created_by(FK),created_at,updated_at
- Rationale: Enables consistent, branded communication.
- Purpose: Stores user email signatures.
- Fields:
id,user(FK),name,content_html,is_default,created_at,updated_at
- Rationale: Allows users to manage multiple signatures.
system_folders = [
{'name': 'Inbox', 'folder_type': 'inbox'},
{'name': 'Sent', 'folder_type': 'sent'},
# ...
]
for folder_data in system_folders:
folder, created = EmailFolder.objects.get_or_create(
owner=request.user,
folder_type=folder_data['folder_type'],
defaults={
'name': folder_data['name'],
'is_system_folder': True
}
)- Purpose: Ensures every user has the standard set of folders.
- Validate and create
Emailobject. - Create
EmailRecipientobjects for all recipients. - Attach files via
EmailAttachment. - Place email in sender's Sent folder and recipients' Inbox via
EmailUserFolder. - Update status fields as delivery progresses.
- All access is filtered by tenant and user role (Super Admin, Tenant Admin, Employee).
- Permissions are enforced at both the queryset and object level.
- Example:
# Only allow access to own folders
if obj.owner != user or obj.owner.tenant != user.tenant:
return FalseAll endpoints are under /api/crm/communication/ (see COMMUNICATION_URL_CONFIGURATION_GUIDE.md).
GET /emails/— List emails (supports filters: folder, search, status, is_read, is_starred)POST /emails/— Create/send emailGET /emails/{id}/— Retrieve email detailsPUT/PATCH /emails/{id}/— Update emailDELETE /emails/{id}/— Delete emailPOST /emails/{id}/perform_action/— Mark as read, star, move, etc.POST /emails/{id}/reply/— Reply to emailPOST /emails/{id}/forward/— Forward email
GET /folders/— List foldersPOST /folders/— Create folderGET /folders/{id}/— Folder detailsPUT/PATCH /folders/{id}/— Update folderDELETE /folders/{id}/— Delete folderPOST /folders/create-system/— Create system folders for user
GET /templates/— List templatesPOST /templates/— Create templateGET /templates/{id}/— Template detailsPUT/PATCH /templates/{id}/— Update templateDELETE /templates/{id}/— Delete template
GET /signatures/— List signaturesPOST /signatures/— Create signatureGET /signatures/{id}/— Signature detailsPUT/PATCH /signatures/{id}/— Update signatureDELETE /signatures/{id}/— Delete signaturePOST /signatures/{id}/set-default/— Set as default
GET /attachments/— List attachmentsGET /attachments/{id}/— Attachment details
GET /stats/— General statsGET /stats/dashboard/— Dashboard statistics
- Multi-Tenant Isolation: All models link to user/tenant, ensuring strict data isolation.
- System vs. Custom Folders: System folders are required for standard UX; custom folders provide flexibility.
- Recipient Tracking: Separate model for recipients allows per-user delivery/read tracking and error handling.
- User-Folder Mapping:
EmailUserFolderenables each user to organize emails independently (move, star, read, etc.). - Permission Classes: All access is filtered by tenant and user role, with super admin bypass for cross-tenant management.
- Extensibility: Modular structure allows for easy addition of new features (e.g., labels, rules, external integrations).
folder, created = EmailFolder.objects.get_or_create(
owner=request.user,
folder_type='inbox',
defaults={'name': 'Inbox', 'is_system_folder': True}
)- See
COMMUNICATION_PERMISSIONS_REVIEW_SUMMARY.mdfor permission logic. - See
COMMUNICATION_URL_CONFIGURATION_GUIDE.mdfor full endpoint list and usage. - See
serializers.pyfor API request/response formats.
This documentation is auto-generated and should be kept up to date with model and API changes.# Communication Module Documentation
The Communication Module is a comprehensive email management system built for a multi-tenant ERP application. It provides functionality for sending, receiving, and managing emails with proper tenant isolation and user access control. The module supports email threading, folder organization, templates, signatures, and attachments.
This module integrates with the core multi-tenant architecture of the ERP system, ensuring that emails and related data are properly isolated between tenants while allowing for efficient cross-tenant operations when necessary (such as super admin access).
- Models and Relationships
- Permission System
- ViewSets and Business Logic
- API Endpoints
- Utility Services
- Management Commands
- Filters
- Admin Interface
- Integration with Core and Client Modules
The communication module uses the following key relationships:
Email
↓
├── EmailRecipient (many) - tracks individual recipient status
├── EmailAttachment (many) - handles file attachments
├── EmailUserFolder (many) - connects emails to folders for each user
↓
EmailFolder
↓
├── EmailUserFolder (many) - connects folders to emails for each user
├── EmailFolder (parent-child) - supports nested folders
EmailTemplate - standalone with user relationship
EmailSignature - standalone with user relationship
The core model for storing email messages.
class Email(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
# Email headers
subject = models.CharField(max_length=500, blank=True)
from_email = models.EmailField(validators=[EmailValidator()])
to_emails = models.JSONField(default=list) # Stores list of recipient email addresses
cc_emails = models.JSONField(default=list, blank=True) # Carbon copy recipients
bcc_emails = models.JSONField(default=list, blank=True) # Blind carbon copy recipients
reply_to = models.EmailField(blank=True, null=True) # Optional reply-to address
# Email content
content_text = models.TextField(blank=True) # Plain text version
content_html = models.TextField(blank=True) # HTML version for rich formatting
# Email metadata
sender = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='sent_emails' # Allows querying all emails sent by a user
)
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES, # draft, sent, delivered, failed, pending
default='draft'
)
priority = models.CharField(
max_length=20,
choices=PRIORITY_CHOICES, # low, normal, high, urgent
default='normal'
)
is_read = models.BooleanField(default=False) # Global read status (user-specific status in EmailUserFolder)
is_important = models.BooleanField(default=False) # Global importance flag
is_starred = models.BooleanField(default=False) # Global starred flag
# Threading support
thread_id = models.UUIDField(
null=True,
blank=True,
help_text="Groups related emails in a conversation thread"
)
in_reply_to = models.ForeignKey(
'self',
on_delete=models.SET_NULL, # If original email is deleted, replies remain
null=True,
blank=True,
related_name='replies' # Allows querying all replies to an email
)
# Timestamps
scheduled_at = models.DateTimeField(
null=True,
blank=True,
help_text="For emails scheduled to be sent in the future"
)
sent_at = models.DateTimeField(null=True, blank=True)
delivered_at = models.DateTimeField(null=True, blank=True)
read_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# External email support
message_id = models.CharField(
max_length=255,
blank=True,
help_text="External email message ID for SMTP integration"
)
raw_email = models.TextField(
blank=True,
help_text="Raw email data for external emails"
)- UUID Primary Key: Ensures globally unique identifiers for emails across tenants
- JSON Fields for Recipients: Stores email addresses as JSON arrays for flexibility
- Threading Support:
thread_idandin_reply_tofields enable conversation threading - Scheduling:
scheduled_atfield allows for delayed sending - Status Tracking: Comprehensive status tracking with timestamps
- Tenant Isolation: Enforced through the
senderrelationship to User model
save(): Automatically setsfrom_emailto sender's email and manages thread IDsrecipient_count: Property that returns total number of recipientshas_attachments: Property that checks if email has attachments
Tracks individual recipient status for each email, enabling detailed delivery tracking.
class EmailRecipient(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.ForeignKey(
Email,
on_delete=models.CASCADE,
related_name='recipients' # Allows querying all recipients of an email
)
recipient_email = models.EmailField() # The actual email address
recipient_user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL, # If user is deleted, keep the recipient record
null=True,
blank=True,
related_name='received_emails' # Allows querying all emails received by a user
)
recipient_type = models.CharField(
max_length=3,
choices=RECIPIENT_TYPES # to, cc, bcc
)
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES, # pending, sent, delivered, read, failed, bounced
default='pending'
)
# Tracking timestamps for detailed delivery status
sent_at = models.DateTimeField(null=True, blank=True)
delivered_at = models.DateTimeField(null=True, blank=True)
read_at = models.DateTimeField(null=True, blank=True)
bounced_at = models.DateTimeField(null=True, blank=True)
# Error tracking
error_message = models.TextField(blank=True) # Stores delivery error details
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)- Individual Status Tracking: Each recipient has its own delivery status
- User Association: Links recipients to system users when possible
- Recipient Type: Distinguishes between TO, CC, and BCC recipients
- Error Handling: Captures delivery errors for troubleshooting
- Timestamp Tracking: Records when emails are sent, delivered, and read
Organizes emails into folders like Inbox, Sent, Drafts, etc.
class EmailFolder(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=100) # Display name of the folder
folder_type = models.CharField(
max_length=20,
choices=FOLDER_TYPES # inbox, sent, drafts, trash, archive, spam, custom
)
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='email_folders' # Allows querying all folders owned by a user
)
parent_folder = models.ForeignKey(
'self', # Self-reference for nested folders
on_delete=models.CASCADE, # Delete subfolders when parent is deleted
null=True,
blank=True,
related_name='subfolders' # Allows querying all subfolders of a folder
)
is_system_folder = models.BooleanField(
default=False, # True for standard folders like Inbox, Sent, etc.
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)- User Ownership: Each folder belongs to a specific user
- Folder Types: Predefined system folders and custom folders
- Hierarchical Structure: Support for nested folders via self-reference
- System Flag: Distinguishes between system and user-created folders
- Tenant Isolation: Enforced through the
ownerrelationship to User model
Many-to-many relationship between emails and folders for each user, with user-specific metadata.
class EmailUserFolder(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.ForeignKey(
Email,
on_delete=models.CASCADE,
related_name='user_folders' # Allows querying all folder assignments for an email
)
folder = models.ForeignKey(
EmailFolder,
on_delete=models.CASCADE,
related_name='emails' # Allows querying all emails in a folder
)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='email_folder_assignments' # Allows querying all email-folder assignments for a user
)
# User-specific email metadata - allows different users to have different states for the same email
is_read = models.BooleanField(default=False)
is_starred = models.BooleanField(default=False)
is_important = models.BooleanField(default=False)
read_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)- Many-to-Many Relationship: Connects emails to folders for each user
- User-Specific Metadata: Each user can have different read/star/important states for the same email
- Flexible Organization: Emails can be in multiple folders for different users
- Read Tracking: Records when a user reads an email
Handles file attachments for emails.
class EmailAttachment(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.ForeignKey(
Email,
on_delete=models.CASCADE,
related_name='attachments' # Allows querying all attachments for an email
)
file = models.FileField(
upload_to='email_attachments/%Y/%m/%d/' # Organizes files by date
)
filename = models.CharField(max_length=255) # Original filename
content_type = models.CharField(max_length=100) # MIME type
file_size = models.PositiveIntegerField() # Size in bytes
is_inline = models.BooleanField(
default=False,
help_text="True for inline images in HTML emails"
)
content_id = models.CharField(
max_length=255,
blank=True,
help_text="Content ID for inline attachments in HTML emails"
)
created_at = models.DateTimeField(auto_now_add=True)- File Storage: Handles file uploads with proper organization
- Metadata: Stores filename, content type, and size
- Inline Support: Special handling for inline images in HTML emails
- Content ID: For referencing inline attachments in HTML content
Reusable email templates with variable support.
class EmailTemplate(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=200) # Template name
description = models.TextField(blank=True) # Optional description
subject_template = models.CharField(max_length=500) # Subject with variables
content_html = models.TextField() # HTML content with variables
content_text = models.TextField(blank=True) # Plain text version with variables
template_type = models.CharField(
max_length=20,
choices=TEMPLATE_TYPES, # user, system, tenant
default='user'
)
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='email_templates' # Allows querying all templates created by a user
)
# Template variables (JSON field to store variable definitions)
variables = models.JSONField(
default=dict,
help_text="Template variables with default values"
)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)- Variable Support: JSON field for template variables
- Multiple Content Formats: Both HTML and plain text versions
- Template Types: User-specific, system-wide, or tenant-wide templates
- Creator Tracking: Links templates to their creators
- Tenant Isolation: Enforced through the
created_byrelationship to User model
User email signatures.
class EmailSignature(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='email_signatures' # Allows querying all signatures for a user
)
name = models.CharField(max_length=100) # Signature name
content_html = models.TextField() # HTML version
content_text = models.TextField(blank=True) # Plain text version
is_default = models.BooleanField(default=False) # Whether this is the user's default signature
is_active = models.BooleanField(default=True) # Whether the signature is active
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)- User Association: Each signature belongs to a specific user
- Multiple Formats: Both HTML and plain text versions
- Default Flag: Users can set a default signature
- Active Flag: Signatures can be deactivated without deletion
- Custom Save Method: Ensures only one default signature per user
The Communication Module implements a comprehensive permission system that ensures proper tenant isolation and user access control. The permission system is built on top of Django REST Framework's permission classes and integrates with the core tenant permission system.
TenantPermission (from core)
↓
CommunicationBasePermission
↓
├── EmailPermission
├── EmailFolderPermission
├── EmailTemplatePermission
├── EmailSignaturePermission
└── EmailAttachmentPermission
TenantAdminPermission (from core)
↓
CommunicationAdminPermission
EmployeePermission (from core)
↓
CommunicationEmployeePermission
Base permission class for all communication-related permissions. Inherits from TenantPermission to ensure tenant isolation.
class CommunicationBasePermission(TenantPermission):
def _validate_communication_access(self, request, obj) -> bool:
"""Validate communication object access with tenant isolation"""
user = request.user
# Super admins can access everything
if user.is_super_admin:
return True
# Check if user has tenant
if not user.tenant:
self._log_permission_violation(
request,
"User without tenant attempting communication access",
object_type=obj.__class__.__name__
)
return False
# Object-specific access rules based on object type
# (Email, Folder, Template, Signature, etc.)
# ...- Tenant Validation: Ensures users can only access data within their tenant
- Super Admin Access: Super admins can access all data across tenants
- Object-Type Detection: Dynamically checks access based on object type
- Violation Logging: Logs permission violations for security auditing
- Flexible Access Rules: Different rules for different object types
class EmailPermission(CommunicationBasePermission):
def has_permission(self, request, view):
"""Check basic email permission"""
if not super().has_permission(request, view):
return False
# All authenticated users within tenant can use email functionality
return True
def has_object_permission(self, request, view, obj):
"""Object-level permission for emails"""
# First check basic tenant permissions
if not super().has_object_permission(request, view, obj):
return False
user = request.user
# Super admins can access everything
if user.is_super_admin:
return True
# Users can access emails they sent
if obj.sender == user:
return True
# Users can access emails they received
if obj.recipients.filter(recipient_user=user).exists():
return True
# Tenant admins can access all emails within their tenant
if user.is_tenant_admin and user.tenant:
# Check if sender belongs to same tenant
if hasattr(obj.sender, 'tenant') and obj.sender.tenant == user.tenant:
return True
# Check if any recipient is in same tenant
if obj.recipients.filter(recipient_user__tenant=user.tenant).exists():
return True
# Employee additional checks
# ...
return False
def get_queryset_filter(self, request, queryset):
"""Filter queryset based on user permissions"""
user = request.user
if user.is_super_admin:
return queryset
if not user.tenant:
return queryset.none()
if user.is_tenant_admin:
# Tenant admin can see all emails in their tenant
return queryset.filter(
Q(sender__tenant=user.tenant) |
Q(recipients__recipient_user__tenant=user.tenant)
).distinct()
elif user.is_employee:
# Employees can only see emails they sent or received
return queryset.filter(
Q(sender=user) |
Q(recipients__recipient_user=user)
).distinct()
return queryset.none()- Three-Level Permission Check:
- View-level permission (
has_permission) - Object-level permission (
has_object_permission) - Query filtering (
get_queryset_filter)
- View-level permission (
- Role-Based Access:
- Super admins: Access to all emails
- Tenant admins: Access to all emails within their tenant
- Employees: Access to emails they sent or received
- Sender/Recipient Validation: Checks both sender and recipient relationships
- Tenant Isolation: Ensures emails are isolated between tenants
Similar permission classes exist for other models (EmailFolder, EmailTemplate, EmailSignature, EmailAttachment) with model-specific access rules.
class CommunicationAdminPermission(TenantAdminPermission):
"""
Permission for communication administration within tenant.
Tenant admins can manage all communication within their tenant.
"""
def has_permission(self, request, view):
"""Check basic admin permission for communication"""
if not super().has_permission(request, view):
return False
# Only tenant admins and super admins can use admin functions
return request.user.is_super_admin or request.user.is_tenant_admin
# Additional methods for object permission and queryset filtering
# ...class CommunicationEmployeePermission(EmployeePermission):
"""
Permission for employee access to communication features.
Employees can access communication within their branch/tenant scope.
"""
def has_permission(self, request, view):
"""Check basic employee permission for communication"""
if not super().has_permission(request, view):
return False
# All authenticated employees can use communication features
return True
# Additional methods for object permission and queryset filtering
# ...The module includes a factory function for dynamically creating permissions:
def create_communication_permission(model_name: str, permission_type: str = 'standard'):
"""
Factory function to create dynamic communication permissions.
Args:
model_name: Name of the model (email, folder, template, etc.)
permission_type: Type of permission (standard, admin, employee)
"""
permission_classes = {
'email': {
'standard': EmailPermission,
'admin': CommunicationAdminPermission,
'employee': CommunicationEmployeePermission,
},
# Other models...
}
return permission_classes.get(model_name, {}).get(permission_type, CommunicationBasePermission)This factory allows for flexible permission assignment based on model type and user role.
The communication module implements a set of ViewSets that handle the API endpoints and business logic. Each ViewSet is responsible for a specific model and includes custom actions for model-specific operations.
Handles CRUD operations for emails with additional actions for email-specific operations.
class EmailViewSet(viewsets.ModelViewSet):
permission_classes = [EmailPermission]
pagination_class = EmailPagination
def get_serializer_class(self):
"""Returns different serializers based on action"""
if self.action == 'list':
return EmailListSerializer # Lightweight serializer for list views
elif self.action == 'create':
return EmailCreateSerializer # Specialized serializer for email creation
return EmailDetailSerializer # Detailed serializer for retrieve/update
def get_queryset(self):
"""Applies permission filtering and query parameters"""
user = self.request.user
queryset = Email.objects.select_related('sender').prefetch_related(
'attachments',
'recipients',
Prefetch(
'user_folders',
queryset=EmailUserFolder.objects.filter(user=user),
to_attr='user_folder_list'
)
)
# Apply permission-based filtering
permission = self.get_permissions()[0]
if hasattr(permission, 'get_queryset_filter'):
queryset = permission.get_queryset_filter(self.request, queryset)
# Apply additional filters (folder, search, status, etc.)
# ...
return queryset.order_by('-created_at')
def retrieve(self, request, *args, **kwargs):
"""Marks email as read when retrieved"""
instance = self.get_object()
# Mark as read for the current user
user_folder, created = EmailUserFolder.objects.get_or_create(
email=instance,
user=request.user,
defaults={'folder': self._get_inbox_folder(request.user)}
)
if not user_folder.is_read:
user_folder.is_read = True
user_folder.read_at = timezone.now()
user_folder.save()
serializer = self.get_serializer(instance)
return Response(serializer.data)
@action(detail=True, methods=['post'])
def perform_action(self, request, pk=None):
"""Handles various email actions (mark as read, star, etc.)"""
email = self.get_object()
serializer = EmailActionSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
action_type = serializer.validated_data['action']
# Get or create user folder for this email
user_folder, created = EmailUserFolder.objects.get_or_create(
email=email,
user=request.user,
defaults={'folder': self._get_inbox_folder(request.user)}
)
# Perform the requested action
if action_type == 'mark_read':
user_folder.is_read = True
user_folder.read_at = timezone.now()
elif action_type == 'mark_unread':
user_folder.is_read = False
user_folder.read_at = None
# Other actions...
user_folder.save()
return Response({'status': 'success'})
@action(detail=True, methods=['post'])
def reply(self, request, pk=None):
"""Creates reply emails"""
# Implementation...
@action(detail=True, methods=['post'])
def send(self, request, pk=None):
"""Sends draft emails"""
# Implementation...- Dynamic Serializers: Different serializers for different actions
- Optimized Queries: Uses
select_relatedandprefetch_relatedfor performance - Permission-Based Filtering: Applies permission filters to querysets
- Custom Actions: Additional endpoints for email-specific operations
- Automatic Read Tracking: Marks emails as read when retrieved
Similar ViewSets exist for other models (EmailFolder, EmailTemplate, EmailSignature, EmailAttachment) with model-specific operations.
The communication module exposes a comprehensive set of RESTful API endpoints for interacting with the email system.
| Method | Endpoint | Description | Permission |
|---|---|---|---|
| GET | /emails/ |
List emails with filtering | EmailPermission |
| POST | /emails/ |
Create a new email | EmailPermission |
| GET | /emails/{id}/ |
Retrieve email details | EmailPermission |
| PUT | /emails/{id}/ |
Update email | EmailPermission |
| PATCH | /emails/{id}/ |
Partial update email | EmailPermission |
| DELETE | /emails/{id}/ |
Delete email | EmailPermission |
| POST | /emails/{id}/action/ |
Perform action on email | EmailPermission |
| POST | /emails/{id}/reply/ |
Reply to email | EmailPermission |
| POST | /emails/{id}/send/ |
Send draft email | EmailPermission |
| Method | Endpoint | Description | Permission |
|---|---|---|---|
| GET | /folders/ |
List folders | EmailFolderPermission |
| POST | /folders/ |
Create folder | EmailFolderPermission |
| GET | /folders/{id}/ |
Retrieve folder details | EmailFolderPermission |
| PUT | /folders/{id}/ |
Update folder | EmailFolderPermission |
| DELETE | /folders/{id}/ |
Delete folder | EmailFolderPermission |
| POST | /folders/create-system/ |
Create system folders | EmailFolderPermission |
| Method | Endpoint | Description | Permission |
|---|---|---|---|
| GET | /templates/ |
List templates | EmailTemplatePermission |
| POST | /templates/ |
Create template | EmailTemplatePermission |
| GET | /templates/{id}/ |
Retrieve template details | EmailTemplatePermission |
| PUT | /templates/{id}/ |
Update template | EmailTemplatePermission |
| DELETE | /templates/{id}/ |
Delete template | EmailTemplatePermission |
| Method | Endpoint | Description | Permission |
|---|---|---|---|
| GET | /signatures/ |
List signatures | EmailSignaturePermission |
| POST | /signatures/ |
Create signature | EmailSignaturePermission |
| GET | /signatures/{id}/ |
Retrieve signature details | EmailSignaturePermission |
| PUT | /signatures/{id}/ |
Update signature | EmailSignaturePermission |
| DELETE | /signatures/{id}/ |
Delete signature | EmailSignaturePermission |
| POST | /signatures/{id}/set-default/ |
Set signature as default | EmailSignaturePermission |
| Method | Endpoint | Description | Permission |
|---|---|---|---|
| GET | /attachments/ |
List attachments | EmailAttachmentPermission |
| GET | /attachments/{id}/ |
Retrieve attachment details | EmailAttachmentPermission |
| Method | Endpoint | Description | Permission |
|---|---|---|---|
| GET | /stats/dashboard/ |
Get email dashboard statistics | CommunicationEmployeePermission |
The communication module includes several utility services for common email operations.
Service class for email operations.
class EmailService:
@staticmethod
def create_email_from_template(template_id, sender, recipients, context_data=None):
"""Creates an email from a template"""
try:
template = EmailTemplate.objects.get(id=template_id)
# Render subject and content with context
context = Context(context_data or {})
subject = Template(template.subject_template).render(context)
content_html = Template(template.content_html).render(context)
content_text = Template(template.content_text).render(context) if template.content_text else ""
# Create email
email = Email.objects.create(
subject=subject,
content_html=content_html,
content_text=content_text,
sender=sender,
from_email=sender.email,
to_emails=recipients
)
return email
except EmailTemplate.DoesNotExist:
raise ValueError(f"Email template with ID {template_id} not found")
@staticmethod
def add_signature_to_email(email, signature_id=None):
"""Adds signature to email content"""
# Implementation...
@staticmethod
def move_email_to_folder(email, user, folder_name_or_id):
"""Moves an email to a specific folder"""
# Implementation...
@staticmethod
def mark_email_as_read(email, user):
"""Marks an email as read"""
# Implementation...
@staticmethod
def get_email_thread(email):
"""Gets all emails in the same thread"""
# Implementation...
@staticmethod
def create_reply_email(original_email, sender, reply_type='reply', additional_recipients=None):
"""Creates a reply email"""
# Implementation...
@staticmethod
def search_emails(user, query, folder=None):
"""Searches emails for a user"""
# Implementation...- Template Rendering: Creates emails from templates with variable substitution
- Signature Management: Adds signatures to email content
- Folder Operations: Moves emails between folders
- Read Tracking: Marks emails as read
- Threading Support: Gets all emails in a thread
- Reply Handling: Creates reply emails with proper threading
- Search Functionality: Searches emails across multiple fields
Utility for email validation.
class EmailValidator:
@staticmethod
def validate_email_addresses(email_list):
"""Validates a list of email addresses"""
from django.core.validators import EmailValidator
from django.core.exceptions import ValidationError
validator = EmailValidator()
valid_emails = []
invalid_emails = []
for email in email_list:
try:
validator(email)
valid_emails.append(email)
except ValidationError:
invalid_emails.append(email)
return valid_emails, invalid_emails
@staticmethod
def clean_email_content(content):
"""Cleans email content to remove potentially harmful elements"""
if not content:
return content
# Remove script tags
content = re.sub(r'<script[^>]*>.*?</script>', '', content, flags=re.DOTALL | re.IGNORECASE)
# Remove javascript: links
content = re.sub(r'javascript:', '', content, flags=re.IGNORECASE)
# Remove on* event handlers
content = re.sub(r'\son\w+\s*=\s*["\'][^"\']*["\']', '', content, flags=re.IGNORECASE)
return content- Email Address Validation: Validates email addresses against RFC standards
- Content Cleaning: Removes potentially harmful elements from HTML content
- Security Focus: Prevents XSS attacks in email content
Utility for email statistics.
class EmailStats:
@staticmethod
def get_user_email_stats(user, days=30):
"""Gets email statistics for a user"""
from datetime import timedelta
end_date = timezone.now()
start_date = end_date - timedelta(days=days)
stats = {
'sent_count': Email.objects.filter(
sender=user,
status='sent',
sent_at__gte=start_date
).count(),
'received_count': Email.objects.filter(
recipients__recipient_user=user,
created_at__gte=start_date
).distinct().count(),
'unread_count': EmailUserFolder.objects.filter(
user=user,
is_read=False
).count(),
'starred_count': EmailUserFolder.objects.filter(
user=user,
is_starred=True
).count(),
'draft_count': Email.objects.filter(
sender=user,
status='draft'
).count(),
}
return stats
@staticmethod
def get_folder_stats(user):
"""Gets statistics for all user folders"""
# Implementation...- User Statistics: Gets email statistics for a specific user
- Time-Based Filtering: Filters statistics by time period
- Folder Statistics: Gets statistics for all user folders
- Performance Optimization: Uses efficient database queries
The communication module includes several management commands for administrative tasks.
Creates default email folders for users.
class Command(BaseCommand):
help = 'Creates default email folders for users'
def add_arguments(self, parser):
parser.add_argument(
'--user',
help='Username or email of specific user to create folders for'
)
parser.add_argument(
'--all',
action='store_true',
help='Create folders for all users'
)
def handle(self, *args, **options):
"""Creates default folders for specified users or all users"""
# Implementation...Processes the email queue for sending scheduled emails.
class Command(BaseCommand):
help = 'Processes the email queue for sending scheduled emails'
def add_arguments(self, parser):
parser.add_argument(
'--limit',
type=int,
default=100,
help='Maximum number of emails to process'
)
def handle(self, *args, **options):
"""Processes and sends queued emails"""
# Implementation...The communication module includes several filter sets for advanced filtering of API endpoints.
Filter set for Email model.
class EmailFilter(django_filters.FilterSet):
# Date filters
created_after = django_filters.DateTimeFilter(field_name='created_at', lookup_expr='gte')
created_before = django_filters.DateTimeFilter(field_name='created_at', lookup_expr='lte')
sent_after = django_filters.DateTimeFilter(field_name='sent_at', lookup_expr='gte')
sent_before = django_filters.DateTimeFilter(field_name='sent_at', lookup_expr='lte')
# Text search
search = django_filters.CharFilter(method='filter_search')
# Email properties
has_attachments = django_filters.BooleanFilter(method='filter_has_attachments')
is_read = django_filters.BooleanFilter(method='filter_is_read')
is_starred = django_filters.BooleanFilter(method='filter_is_starred')
is_important = django_filters.BooleanFilter(method='filter_is_important')
# Folder filter
folder = django_filters.UUIDFilter(method='filter_folder')
folder_type = django_filters.CharFilter(method='filter_folder_type')
# Recipients
from_email = django_filters.CharFilter(field_name='from_email', lookup_expr='icontains')
to_email = django_filters.CharFilter(method='filter_to_email')
class Meta:
model = Email
fields = [
'status', 'priority', 'thread_id', 'in_reply_to',
'created_after', 'created_before', 'sent_after', 'sent_before',
'search', 'has_attachments', 'is_read', 'is_starred', 'is_important',
'folder', 'folder_type', 'from_email', 'to_email'
]
def filter_search(self, queryset, name, value):
"""Search across multiple fields"""
if not value:
return queryset
return queryset.filter(
Q(subject__icontains=value) |
Q(content_text__icontains=value) |
Q(content_html__icontains=value) |
Q(from_email__icontains=value) |
Q(to_emails__icontains=value) |
Q(sender__first_name__icontains=value) |
Q(sender__last_name__icontains=value)
)
def filter_has_attachments(self, queryset, name, value):
"""Filter emails with or without attachments"""
if value:
return queryset.filter(attachments__isnull=False).distinct()
else:
return queryset.filter(attachments__isnull=True)
# Other filter methods...Similar filter sets exist for other models (EmailFolder, EmailTemplate) with model-specific filters.
The communication module provides a comprehensive admin interface for managing emails and related objects.
@admin.register(Email)
class EmailAdmin(admin.ModelAdmin):
list_display = [
'subject', 'sender', 'from_email', 'status',
'priority', 'recipient_count', 'has_attachments',
'created_at'
]
list_filter = [
'status', 'priority', 'is_important', 'created_at', 'sent_at'
]
search_fields = [
'subject', 'from_email', 'sender__email',
'content_text', 'to_emails'
]
readonly_fields = [
'id', 'thread_id', 'recipient_count', 'has_attachments',
'sent_at', 'created_at', 'updated_at'
]
inlines = [EmailRecipientInline, EmailAttachmentInline]
fieldsets = (
('Email Information', {
'fields': (
'id', 'subject', 'sender', 'from_email', 'reply_to'
)
}),
('Recipients', {
'fields': ('to_emails', 'cc_emails', 'bcc_emails')
}),
('Content', {
'fields': ('content_text', 'content_html')
}),
('Metadata', {
'fields': (
'status', 'priority', 'is_important', 'thread_id',
'in_reply_to', 'recipient_count', 'has_attachments'
)
}),
('Timestamps', {
'fields': (
'scheduled_at', 'sent_at', 'created_at', 'updated_at'
)
}),
)
def get_queryset(self, request):
return super().get_queryset(request).select_related('sender')Similar admin classes exist for other models (EmailFolder, EmailRecipient, EmailAttachment, EmailTemplate, EmailSignature, EmailUserFolder) with model-specific configurations.
The communication module integrates with the core and client modules of the ERP system to provide a seamless email experience within the multi-tenant architecture.
- User Model: The communication module uses the User model from the core module for sender and recipient relationships.
- Tenant System: The permission system integrates with the core tenant system to ensure proper tenant isolation.
- Permission Classes: The communication permission classes extend the core permission classes.
- Branch Relationship: Emails can be associated with branches from the client module.
- Employee Profiles: Email recipients can be linked to employee profiles.
- Client Context: Emails are sent and received within the context of a client (tenant).
The communication module follows the multi-tenant architecture of the ERP system:
- Tenant Isolation: Emails and related data are isolated between tenants.
- Cross-Tenant Operations: Super admins can perform cross-tenant operations.
- Tenant-Specific Templates: Templates can be tenant-specific or system-wide.
- Tenant Context: All operations are performed within the context of a tenant.
The Communication Module provides a comprehensive email management system with multi-tenant support. It includes features for sending, receiving, and organizing emails, with support for templates, signatures, and attachments. The permission system ensures proper tenant isolation and user access control.
The module is designed to be extensible and can be integrated with other modules in the ERP system. The API endpoints provide a clean interface for frontend applications to interact with the email system.
Key strengths of the implementation include:
- Comprehensive Data Model: Detailed models for all aspects of email communication
- Robust Permission System: Multi-level permission checks with tenant isolation
- Flexible API: RESTful API with filtering, pagination, and custom actions
- Utility Services: Reusable services for common email operations
- Admin Interface: Comprehensive admin interface for managing emails
- Integration: Seamless integration with core and client modules
- Multi-Tenant Support: Full support for the multi-tenant architecture