Skip to content

Instantly share code, notes, and snippets.

@amal-babu-git
Last active July 22, 2025 08:35
Show Gist options
  • Select an option

  • Save amal-babu-git/9b6cee1484d4cdef5f48139652085b56 to your computer and use it in GitHub Desktop.

Select an option

Save amal-babu-git/9b6cee1484d4cdef5f48139652085b56 to your computer and use it in GitHub Desktop.

Communication Module Documentation

Overview

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.


Models & Relationships

EmailFolder

  • Purpose: Organizes emails into system and custom folders per user.
  • Fields:
    • id: UUID, primary key
    • name: 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.

Email

  • Purpose: Stores email messages and metadata.
  • Fields:
    • id: UUID, primary key
    • subject, from_email, to_emails, cc_emails, bcc_emails, reply_to: Email headers
    • content_text, content_html: Email body
    • sender: 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: Flags
    • thread_id, in_reply_to: Threading support
    • scheduled_at, sent_at, delivered_at, read_at: Timestamps
    • message_id, raw_email: External email support
    • created_at, updated_at: Timestamps
  • Relationships:
    • Linked to EmailRecipient (recipients)
    • Linked to EmailAttachment (attachments)
    • Linked to EmailUserFolder (user-folder mapping)
  • Rationale: Supports all standard email features, threading, and multi-user/tenant context.

EmailRecipient

  • Purpose: Tracks each recipient's status for an email.
  • Fields:
    • id: UUID, primary key
    • email: FK to Email
    • recipient_email: Email address
    • recipient_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: Timestamps
    • error_message: Error details
    • created_at, updated_at: Timestamps
  • Rationale: Enables per-recipient delivery/read tracking and error handling.

EmailAttachment

  • 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.

EmailUserFolder

  • 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).

EmailTemplate

  • 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.

EmailSignature

  • 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.

Key Logic & Workflows

System Folder Creation

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.

Email Sending Workflow

  • Validate and create Email object.
  • Create EmailRecipient objects 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.

Permission Enforcement

  • 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 False

REST API Endpoints

All endpoints are under /api/crm/communication/ (see COMMUNICATION_URL_CONFIGURATION_GUIDE.md).

Email Endpoints

  • GET /emails/ — List emails (supports filters: folder, search, status, is_read, is_starred)
  • POST /emails/ — Create/send email
  • GET /emails/{id}/ — Retrieve email details
  • PUT/PATCH /emails/{id}/ — Update email
  • DELETE /emails/{id}/ — Delete email
  • POST /emails/{id}/perform_action/ — Mark as read, star, move, etc.
  • POST /emails/{id}/reply/ — Reply to email
  • POST /emails/{id}/forward/ — Forward email

Folder Endpoints

  • GET /folders/ — List folders
  • POST /folders/ — Create folder
  • GET /folders/{id}/ — Folder details
  • PUT/PATCH /folders/{id}/ — Update folder
  • DELETE /folders/{id}/ — Delete folder
  • POST /folders/create-system/ — Create system folders for user

Template Endpoints

  • GET /templates/ — List templates
  • POST /templates/ — Create template
  • GET /templates/{id}/ — Template details
  • PUT/PATCH /templates/{id}/ — Update template
  • DELETE /templates/{id}/ — Delete template

Signature Endpoints

  • GET /signatures/ — List signatures
  • POST /signatures/ — Create signature
  • GET /signatures/{id}/ — Signature details
  • PUT/PATCH /signatures/{id}/ — Update signature
  • DELETE /signatures/{id}/ — Delete signature
  • POST /signatures/{id}/set-default/ — Set as default

Attachment Endpoints

  • GET /attachments/ — List attachments
  • GET /attachments/{id}/ — Attachment details

Stats Endpoints

  • GET /stats/ — General stats
  • GET /stats/dashboard/ — Dashboard statistics

Design Decisions & Field Links

  • 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: EmailUserFolder enables 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).

Example: Creating a System Folder (Snippet)

folder, created = EmailFolder.objects.get_or_create(
    owner=request.user,
    folder_type='inbox',
    defaults={'name': 'Inbox', 'is_system_folder': True}
)

References

  • See COMMUNICATION_PERMISSIONS_REVIEW_SUMMARY.md for permission logic.
  • See COMMUNICATION_URL_CONFIGURATION_GUIDE.md for full endpoint list and usage.
  • See serializers.py for API request/response formats.

This documentation is auto-generated and should be kept up to date with model and API changes.# Communication Module Documentation

Overview

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).

Table of Contents

Models and Relationships

Data Model Relationships

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

Email

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"
    )

Key Features of Email Model:

  1. UUID Primary Key: Ensures globally unique identifiers for emails across tenants
  2. JSON Fields for Recipients: Stores email addresses as JSON arrays for flexibility
  3. Threading Support: thread_id and in_reply_to fields enable conversation threading
  4. Scheduling: scheduled_at field allows for delayed sending
  5. Status Tracking: Comprehensive status tracking with timestamps
  6. Tenant Isolation: Enforced through the sender relationship to User model

Custom Methods:

  • save(): Automatically sets from_email to sender's email and manages thread IDs
  • recipient_count: Property that returns total number of recipients
  • has_attachments: Property that checks if email has attachments

EmailRecipient

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)

Key Features of EmailRecipient Model:

  1. Individual Status Tracking: Each recipient has its own delivery status
  2. User Association: Links recipients to system users when possible
  3. Recipient Type: Distinguishes between TO, CC, and BCC recipients
  4. Error Handling: Captures delivery errors for troubleshooting
  5. Timestamp Tracking: Records when emails are sent, delivered, and read

EmailFolder

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)

Key Features of EmailFolder Model:

  1. User Ownership: Each folder belongs to a specific user
  2. Folder Types: Predefined system folders and custom folders
  3. Hierarchical Structure: Support for nested folders via self-reference
  4. System Flag: Distinguishes between system and user-created folders
  5. Tenant Isolation: Enforced through the owner relationship to User model

EmailUserFolder

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)

Key Features of EmailUserFolder Model:

  1. Many-to-Many Relationship: Connects emails to folders for each user
  2. User-Specific Metadata: Each user can have different read/star/important states for the same email
  3. Flexible Organization: Emails can be in multiple folders for different users
  4. Read Tracking: Records when a user reads an email

EmailAttachment

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)

Key Features of EmailAttachment Model:

  1. File Storage: Handles file uploads with proper organization
  2. Metadata: Stores filename, content type, and size
  3. Inline Support: Special handling for inline images in HTML emails
  4. Content ID: For referencing inline attachments in HTML content

EmailTemplate

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)

Key Features of EmailTemplate Model:

  1. Variable Support: JSON field for template variables
  2. Multiple Content Formats: Both HTML and plain text versions
  3. Template Types: User-specific, system-wide, or tenant-wide templates
  4. Creator Tracking: Links templates to their creators
  5. Tenant Isolation: Enforced through the created_by relationship to User model

EmailSignature

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)

Key Features of EmailSignature Model:

  1. User Association: Each signature belongs to a specific user
  2. Multiple Formats: Both HTML and plain text versions
  3. Default Flag: Users can set a default signature
  4. Active Flag: Signatures can be deactivated without deletion
  5. Custom Save Method: Ensures only one default signature per user

Permission System

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.

Permission Hierarchy

TenantPermission (from core)
    ↓
CommunicationBasePermission
    ↓
    ├── EmailPermission
    ├── EmailFolderPermission
    ├── EmailTemplatePermission
    ├── EmailSignaturePermission
    └── EmailAttachmentPermission

TenantAdminPermission (from core)
    ↓
CommunicationAdminPermission

EmployeePermission (from core)
    ↓
CommunicationEmployeePermission

Base Permission Class

CommunicationBasePermission

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.)
        # ...

Key Features of CommunicationBasePermission:

  1. Tenant Validation: Ensures users can only access data within their tenant
  2. Super Admin Access: Super admins can access all data across tenants
  3. Object-Type Detection: Dynamically checks access based on object type
  4. Violation Logging: Logs permission violations for security auditing
  5. Flexible Access Rules: Different rules for different object types

Model-Specific Permissions

EmailPermission

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()

Key Features of EmailPermission:

  1. Three-Level Permission Check:
    • View-level permission (has_permission)
    • Object-level permission (has_object_permission)
    • Query filtering (get_queryset_filter)
  2. 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
  3. Sender/Recipient Validation: Checks both sender and recipient relationships
  4. Tenant Isolation: Ensures emails are isolated between tenants

Similar permission classes exist for other models (EmailFolder, EmailTemplate, EmailSignature, EmailAttachment) with model-specific access rules.

Role-Based Permissions

CommunicationAdminPermission

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
    # ...

CommunicationEmployeePermission

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
    # ...

Permission Factory

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.

ViewSets and Business Logic

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.

EmailViewSet

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...

Key Features of EmailViewSet:

  1. Dynamic Serializers: Different serializers for different actions
  2. Optimized Queries: Uses select_related and prefetch_related for performance
  3. Permission-Based Filtering: Applies permission filters to querysets
  4. Custom Actions: Additional endpoints for email-specific operations
  5. Automatic Read Tracking: Marks emails as read when retrieved

Similar ViewSets exist for other models (EmailFolder, EmailTemplate, EmailSignature, EmailAttachment) with model-specific operations.

API Endpoints

The communication module exposes a comprehensive set of RESTful API endpoints for interacting with the email system.

Email Endpoints

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

Folder Endpoints

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

Template Endpoints

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

Signature Endpoints

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

Attachment Endpoints

Method Endpoint Description Permission
GET /attachments/ List attachments EmailAttachmentPermission
GET /attachments/{id}/ Retrieve attachment details EmailAttachmentPermission

Statistics Endpoints

Method Endpoint Description Permission
GET /stats/dashboard/ Get email dashboard statistics CommunicationEmployeePermission

Utility Services

The communication module includes several utility services for common email operations.

EmailService

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...

Key Features of EmailService:

  1. Template Rendering: Creates emails from templates with variable substitution
  2. Signature Management: Adds signatures to email content
  3. Folder Operations: Moves emails between folders
  4. Read Tracking: Marks emails as read
  5. Threading Support: Gets all emails in a thread
  6. Reply Handling: Creates reply emails with proper threading
  7. Search Functionality: Searches emails across multiple fields

EmailValidator

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

Key Features of EmailValidator:

  1. Email Address Validation: Validates email addresses against RFC standards
  2. Content Cleaning: Removes potentially harmful elements from HTML content
  3. Security Focus: Prevents XSS attacks in email content

EmailStats

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...

Key Features of EmailStats:

  1. User Statistics: Gets email statistics for a specific user
  2. Time-Based Filtering: Filters statistics by time period
  3. Folder Statistics: Gets statistics for all user folders
  4. Performance Optimization: Uses efficient database queries

Management Commands

The communication module includes several management commands for administrative tasks.

create_email_folders

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...

process_email_queue

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...

Filters

The communication module includes several filter sets for advanced filtering of API endpoints.

EmailFilter

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.

Admin Interface

The communication module provides a comprehensive admin interface for managing emails and related objects.

EmailAdmin

@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.

Integration with Core and Client Modules

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.

Core Module Integration

  1. User Model: The communication module uses the User model from the core module for sender and recipient relationships.
  2. Tenant System: The permission system integrates with the core tenant system to ensure proper tenant isolation.
  3. Permission Classes: The communication permission classes extend the core permission classes.

Client Module Integration

  1. Branch Relationship: Emails can be associated with branches from the client module.
  2. Employee Profiles: Email recipients can be linked to employee profiles.
  3. Client Context: Emails are sent and received within the context of a client (tenant).

Multi-Tenant Architecture

The communication module follows the multi-tenant architecture of the ERP system:

  1. Tenant Isolation: Emails and related data are isolated between tenants.
  2. Cross-Tenant Operations: Super admins can perform cross-tenant operations.
  3. Tenant-Specific Templates: Templates can be tenant-specific or system-wide.
  4. Tenant Context: All operations are performed within the context of a tenant.

Conclusion

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:

  1. Comprehensive Data Model: Detailed models for all aspects of email communication
  2. Robust Permission System: Multi-level permission checks with tenant isolation
  3. Flexible API: RESTful API with filtering, pagination, and custom actions
  4. Utility Services: Reusable services for common email operations
  5. Admin Interface: Comprehensive admin interface for managing emails
  6. Integration: Seamless integration with core and client modules
  7. Multi-Tenant Support: Full support for the multi-tenant architecture
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment