Skip to content

Email Notifications - Gap Analysis & Implementation Plan

Date: December 31, 2024
Feature: Outbound Email Notifications (Phase 3)
Priority: High
Estimated Time: 8-10 hours


Executive Summary

Extend the existing email integration to support outbound notifications across all modules. Current implementation only handles inbound email processing (Phase 2). This plan adds automated email sending for business events.

Current State: Inbound email processing only (Sales Order creation from emails)
Target State: Bi-directional email integration (inbound + outbound notifications)


Gap Analysis

Current Infrastructure ✅

  • ✅ EmailConfig model with SMTP settings per module
  • ✅ SMTP connection logic in email_processor.py
  • ✅ Email encryption for credentials
  • ✅ Company Profile UI for email configuration
  • ✅ Multi-module support (Sales, Purchase, Accounts, Inventory, HR)

Missing Components ❌

  • ❌ Email template system
  • ❌ Notification trigger logic (when to send)
  • ❌ Recipient management (who to send to)
  • ❌ Email queue/retry mechanism
  • ❌ Email history/audit trail for sent emails
  • ❌ Unsubscribe/preference management

Requested Features

1. HR Management - Commission Note Email

Trigger: Commission note created/approved
Recipient: Salesman
Content: Commission details, period, amount, breakdown
Attachment: PDF commission note
Priority: Medium

Current Status: ❌ Not implemented
Gap: No email trigger on commission creation


2. Purchase - Purchase Order to Supplier

Trigger: Purchase Order confirmed
Recipient: Vendor email
Content: PO details, line items, delivery date
Attachment: PDF purchase order
Priority: High

Current Status: ❌ Not implemented
Gap: No email trigger on PO confirmation


3. Inventory - Stock Alert to Manager

Trigger: Stock falls below minimum level during dispatch
Recipient: Warehouse manager / Inventory manager
Content: Product name, current stock, minimum level, warehouse
Attachment: None
Priority: High

Current Status: ❌ Not implemented
Gap: No stock level monitoring, no alert system


4. Accounts - Invoice Due for Collection

Trigger: Invoice due date approaching (e.g., 3 days before)
Recipient: Manager / Accounts receivable team
Content: Customer name, invoice number, amount, due date
Attachment: PDF invoice
Priority: Medium

Current Status: ❌ Not implemented
Gap: No scheduled task for due date checking


5. Accounts - Bill Due for Payment

Trigger: Vendor bill due date approaching (e.g., 3 days before)
Recipient: Manager / Accounts payable team
Content: Vendor name, bill number, amount, due date
Attachment: PDF bill
Priority: Medium

Current Status: ❌ Not implemented
Gap: No scheduled task for due date checking


Proposed Architecture

1. Email Template System

New Model: EmailTemplate

class EmailTemplate(models.Model):
    company = ForeignKey(Company)
    code = CharField(choices=[
        ('COMMISSION_NOTE', 'Commission Note'),
        ('PURCHASE_ORDER', 'Purchase Order'),
        ('STOCK_ALERT', 'Stock Alert'),
        ('INVOICE_DUE', 'Invoice Due for Collection'),
        ('BILL_DUE', 'Bill Due for Payment'),
    ])
    subject = CharField(max_length=255)
    body_html = TextField()  # HTML template with placeholders
    body_text = TextField()  # Plain text version
    is_active = BooleanField(default=True)

    # Placeholders: {{company_name}}, {{amount}}, {{due_date}}, etc.

Default Templates: Provide professional templates for each notification type


2. Email Notification Service

New Service: core/services/email_notification.py

class EmailNotificationService:
    def send_commission_note(commission_note_id)
    def send_purchase_order(po_id)
    def send_stock_alert(product_id, warehouse_id)
    def send_invoice_due_reminder(invoice_id)
    def send_bill_due_reminder(bill_id)

    def _send_email(template_code, recipient, context, attachments)
    def _render_template(template, context)
    def _generate_pdf_attachment(model_instance)

3. Notification Triggers

A. Event-Based Triggers (Signals)

# HR: Commission Note Created
@receiver(post_save, sender=CommissionNote)
def send_commission_email(sender, instance, created, **kwargs):
    if instance.state == 'APPROVED':
        EmailNotificationService.send_commission_note(instance.id)

# Purchases: PO Confirmed
@receiver(post_save, sender=PurchaseOrder)
def send_po_email(sender, instance, created, **kwargs):
    if instance.state == 'CONFIRMED' and not instance.email_sent:
        EmailNotificationService.send_purchase_order(instance.id)
        instance.email_sent = True
        instance.save()

# Inventory: Stock Level Check (on stock move)
@receiver(post_save, sender=StockMove)
def check_stock_alert(sender, instance, **kwargs):
    if instance.state == 'DONE':
        # Check if stock fell below minimum
        EmailNotificationService.send_stock_alert(...)

B. Scheduled Tasks (Celery Beat)

# Daily task: Check due invoices/bills
@shared_task
def check_due_invoices():
    """Run daily at 9 AM"""
    due_date = timezone.now().date() + timedelta(days=3)
    invoices = Invoice.objects.filter(
        due_date=due_date,
        state='POSTED',
        payment_state__in=['NOT_PAID', 'PARTIAL']
    )
    for invoice in invoices:
        EmailNotificationService.send_invoice_due_reminder(invoice.id)

@shared_task
def check_due_bills():
    """Run daily at 9 AM"""
    # Similar logic for vendor bills

4. Email History Model

New Model: EmailHistory

class EmailHistory(models.Model):
    company = ForeignKey(Company)
    template_code = CharField(max_length=50)
    recipient = EmailField()
    subject = CharField(max_length=500)
    body = TextField()

    # Related object (polymorphic)
    content_type = ForeignKey(ContentType)
    object_id = UUIDField()
    content_object = GenericForeignKey()

    # Status
    status = CharField(choices=[
        ('QUEUED', 'Queued'),
        ('SENT', 'Sent'),
        ('FAILED', 'Failed'),
        ('BOUNCED', 'Bounced'),
    ])
    sent_at = DateTimeField(null=True)
    error_message = TextField(blank=True)

    # Metadata
    attachment_count = IntegerField(default=0)
    opened_at = DateTimeField(null=True)  # Future: tracking

Implementation Plan

Phase 3A: Email Template System (2-3 hours)

Backend

  1. Create Models:
  2. EmailTemplate model
  3. EmailHistory model
  4. Migrations

  5. Create Default Templates:

  6. Commission Note template
  7. Purchase Order template
  8. Stock Alert template
  9. Invoice Due template
  10. Bill Due template

  11. Template Rendering:

  12. Jinja2 or Django template engine
  13. Context variables for each template type
  14. HTML + Plain text versions

Frontend

  1. Email Template Management UI:
  2. List templates
  3. Edit template (WYSIWYG editor)
  4. Preview with sample data
  5. Available placeholders guide

Phase 3B: Notification Service (2-3 hours)

Backend

  1. Create Service Layer:
  2. EmailNotificationService class
  3. Send methods for each notification type
  4. PDF generation integration
  5. SMTP sending via existing EmailConfig

  6. PDF Generation:

  7. Reuse existing print templates
  8. Generate PDF from HTML
  9. Attach to email

  10. Error Handling:

  11. Retry logic (3 attempts)
  12. Error logging
  13. Fallback to admin notification

Phase 3C: Event Triggers (2-3 hours)

Backend

  1. Django Signals:
  2. Commission note approval → Email
  3. PO confirmation → Email
  4. Stock move completion → Check alert

  5. Add Fields to Models:

  6. PurchaseOrder.email_sent (Boolean)
  7. Product.minimum_stock_level (Decimal)
  8. Product.stock_alert_enabled (Boolean)

  9. Celery Scheduled Tasks:

  10. Daily task for invoice due reminders
  11. Daily task for bill due reminders
  12. Configure Celery Beat schedule

Phase 3D: Frontend UI (1-2 hours)

Frontend

  1. Email History Page:
  2. Table view of sent emails
  3. Filter by template type, status, date
  4. View email content
  5. Resend failed emails

  6. Notification Preferences:

  7. Per-user email preferences
  8. Unsubscribe from certain notifications
  9. Email frequency settings

  10. Email Preview:

  11. Preview email before sending
  12. Test email functionality

Detailed Implementation

1. Commission Note Email

Trigger Point

# hrms/views.py - CommissionNoteViewSet
@action(detail=True, methods=['post'])
def approve(self, request, pk=None):
    note = self.get_object()
    note.state = 'APPROVED'
    note.save()

    # Send email to salesman
    EmailNotificationService.send_commission_note(note.id)

    return Response({'status': 'approved'})

Email Template

Subject: Your Commission Note for {{period}}

Dear {{salesman_name}},

Your commission for the period {{period}} has been approved.

Commission Details:
- Total Sales: {{total_sales}}
- Commission Rate: {{commission_rate}}%
- Commission Amount: {{commission_amount}}

Please find the detailed commission note attached.

Best regards,
{{company_name}}

Recipient

  • CommissionNote.employee.work_email
  • Fallback: CommissionNote.employee.personal_email

2. Purchase Order Email

Trigger Point

# purchases/views.py - PurchaseOrderViewSet
@action(detail=True, methods=['post'])
def confirm(self, request, pk=None):
    po = self.get_object()
    po.state = 'CONFIRMED'
    po.confirmed_date = timezone.now()
    po.save()

    # Send email to vendor
    if po.vendor.email and not po.email_sent:
        EmailNotificationService.send_purchase_order(po.id)
        po.email_sent = True
        po.save()

    return Response({'status': 'confirmed'})

Email Template

Subject: Purchase Order {{po_reference}} from {{company_name}}

Dear {{vendor_name}},

Please find attached Purchase Order {{po_reference}}.

Order Details:
- PO Number: {{po_reference}}
- Order Date: {{order_date}}
- Expected Delivery: {{expected_date}}
- Total Amount: {{total_amount}} {{currency}}

Items:
{{#items}}
- {{product_name}}: {{quantity}} {{uom}} @ {{unit_price}}
{{/items}}

Please confirm receipt and expected delivery date.

Best regards,
{{company_name}}

Recipient

  • PurchaseOrder.vendor.email

3. Stock Alert Email

Trigger Point

# inventory/models.py - StockMove signal
@receiver(post_save, sender=StockMove)
def check_stock_alert(sender, instance, **kwargs):
    if instance.state == 'DONE' and instance.location_dest.usage == 'CUSTOMER':
        # Outgoing stock move
        product = instance.product
        warehouse = instance.location.warehouse

        if product.stock_alert_enabled:
            current_stock = StockQuant.get_available_quantity(
                product, warehouse
            )

            if current_stock < product.minimum_stock_level:
                EmailNotificationService.send_stock_alert(
                    product.id, 
                    warehouse.id,
                    current_stock
                )

Email Template

Subject: Stock Alert: {{product_name}} below minimum level

STOCK ALERT

Product: {{product_name}}
Warehouse: {{warehouse_name}}
Current Stock: {{current_stock}} {{uom}}
Minimum Level: {{minimum_level}} {{uom}}
Shortage: {{shortage}} {{uom}}

Action Required: Please reorder immediately.

View Product: {{product_url}}

Recipient

  • Warehouse manager email (from EmailConfig for INVENTORY module)
  • Or: Warehouse.manager.email (if field exists)

4. Invoice Due Reminder

Trigger Point

# Celery scheduled task
@shared_task
def send_invoice_due_reminders():
    """Run daily at 9 AM"""
    reminder_days = 3  # Configurable
    due_date = timezone.now().date() + timedelta(days=reminder_days)

    invoices = Invoice.objects.filter(
        due_date=due_date,
        state='POSTED',
        payment_state__in=['NOT_PAID', 'PARTIAL']
    ).exclude(
        id__in=EmailHistory.objects.filter(
            template_code='INVOICE_DUE',
            sent_at__date=timezone.now().date()
        ).values_list('object_id', flat=True)
    )

    for invoice in invoices:
        EmailNotificationService.send_invoice_due_reminder(invoice.id)

Email Template

Subject: Invoice {{invoice_number}} Due in {{days_until_due}} Days

Dear Manager,

The following invoice is due for collection in {{days_until_due}} days:

Invoice Details:
- Invoice Number: {{invoice_number}}
- Customer: {{customer_name}}
- Invoice Date: {{invoice_date}}
- Due Date: {{due_date}}
- Amount: {{amount}} {{currency}}
- Outstanding: {{outstanding_amount}} {{currency}}

Please follow up with the customer for payment.

View Invoice: {{invoice_url}}

Recipient

  • Manager email (from EmailConfig for ACCOUNTS module)
  • Or: Company admin users

5. Bill Due Reminder

Trigger Point

# Similar to invoice due reminder
@shared_task
def send_bill_due_reminders():
    """Run daily at 9 AM"""
    # Similar logic for vendor bills

Email Template

Subject: Bill {{bill_number}} Due for Payment in {{days_until_due}} Days

Dear Manager,

The following vendor bill is due for payment in {{days_until_due}} days:

Bill Details:
- Bill Number: {{bill_number}}
- Vendor: {{vendor_name}}
- Bill Date: {{bill_date}}
- Due Date: {{due_date}}
- Amount: {{amount}} {{currency}}
- Outstanding: {{outstanding_amount}} {{currency}}

Please arrange payment before the due date.

View Bill: {{bill_url}}

Recipient

  • Manager email (from EmailConfig for ACCOUNTS module)

Database Schema Changes

New Models

1. EmailTemplate

- id (UUID)
- company (FK)
- code (CharField, choices)
- subject (CharField)
- body_html (TextField)
- body_text (TextField)
- is_active (Boolean)
- created_at, updated_at

2. EmailHistory

- id (UUID)
- company (FK)
- template_code (CharField)
- recipient (EmailField)
- subject (CharField)
- body (TextField)
- content_type (FK)
- object_id (UUID)
- status (CharField, choices)
- sent_at (DateTime)
- error_message (TextField)
- attachment_count (Integer)
- created_at

Modified Models

PurchaseOrder

+ email_sent (Boolean, default=False)
+ email_sent_at (DateTime, null=True)

Product

+ minimum_stock_level (Decimal, default=0)
+ stock_alert_enabled (Boolean, default=False)
+ stock_alert_recipients (EmailField, blank=True)  # Comma-separated

API Endpoints

Email Templates

  • GET /core/email-templates/ - List templates
  • POST /core/email-templates/ - Create template
  • PUT /core/email-templates/{id}/ - Update template
  • GET /core/email-templates/{id}/preview/ - Preview with sample data

Email History

  • GET /core/email-history/ - List sent emails
  • GET /core/email-history/{id}/ - View email details
  • POST /core/email-history/{id}/resend/ - Resend failed email

Manual Triggers

  • POST /purchases/purchase-orders/{id}/send_email/ - Manually send PO email
  • POST /hrms/commission-notes/{id}/send_email/ - Manually send commission email

Celery Beat Schedule

app.conf.beat_schedule = {
    'process-incoming-emails': {
        'task': 'sales.tasks.process_incoming_emails',
        'schedule': crontab(minute='*/5'),  # Every 5 minutes
    },
    'send-invoice-due-reminders': {
        'task': 'accounts.tasks.send_invoice_due_reminders',
        'schedule': crontab(hour=9, minute=0),  # Daily at 9 AM
    },
    'send-bill-due-reminders': {
        'task': 'accounts.tasks.send_bill_due_reminders',
        'schedule': crontab(hour=9, minute=0),  # Daily at 9 AM
    },
}

Frontend Components

1. Email Templates Page

Location: Admin → Email Templates

Features: - List all templates - Edit template (rich text editor) - Preview with sample data - Available placeholders guide - Active/Inactive toggle


2. Email History Page

Location: Admin → Email History

Features: - Table view of sent emails - Filter by type, status, date range - Search by recipient - View email content modal - Resend failed emails - Export to CSV


3. Notification Preferences

Location: User Settings → Notifications

Features: - Toggle email notifications per type - Set reminder days for due dates - Unsubscribe options - Email frequency (immediate, daily digest)


Testing Strategy

Unit Tests

  1. Template rendering with various contexts
  2. Email sending logic
  3. Recipient resolution
  4. PDF attachment generation
  5. Error handling and retries

Integration Tests

  1. End-to-end commission note flow
  2. End-to-end PO confirmation flow
  3. Stock alert triggering
  4. Due date reminder scheduling

Manual Testing

  1. Configure SMTP for each module
  2. Trigger each notification type
  3. Verify email received
  4. Check email history
  5. Test retry mechanism

Deployment Checklist

  • [ ] Create migrations for new models
  • [ ] Seed default email templates
  • [ ] Update Celery Beat schedule
  • [ ] Configure SMTP for all modules
  • [ ] Test email sending in staging
  • [ ] Update user documentation
  • [ ] Train users on email preferences

Estimated Timeline

Phase Task Time Total
3A Email Template System 2-3h 2-3h
- Models & Migrations 1h
- Default Templates 1h
- Template UI 1h
3B Notification Service 2-3h 4-6h
- Service Layer 1h
- PDF Generation 1h
- Error Handling 1h
3C Event Triggers 2-3h 6-9h
- Django Signals 1h
- Model Updates 1h
- Celery Tasks 1h
3D Frontend UI 1-2h 7-11h
- Email History Page 1h
- Preferences UI 1h

Total Estimated Time: 8-10 hours


Success Criteria

  • [x] All 5 notification types implemented
  • [x] Email templates customizable per company
  • [x] Email history tracked and viewable
  • [x] Failed emails can be retried
  • [x] Users can manage notification preferences
  • [x] Scheduled tasks running reliably
  • [x] PDF attachments generated correctly
  • [x] SMTP sending working for all modules

Risks & Mitigation

Risk Impact Mitigation
Email delivery failures High Retry mechanism, error logging, admin alerts
SMTP rate limits Medium Queue system, throttling, use email service (SendGrid)
Template rendering errors Medium Validation, preview, fallback templates
Spam filters Medium SPF/DKIM setup, professional templates
User email overload Low Preference management, digest options

Future Enhancements

  1. Email Service Integration: SendGrid, Mailgun, AWS SES
  2. Email Tracking: Open rates, click tracking
  3. A/B Testing: Test different email templates
  4. Scheduled Sending: Send at specific times
  5. Email Campaigns: Bulk email to customers
  6. SMS Notifications: Extend to SMS for critical alerts
  7. WhatsApp Notifications: Business API integration

Conclusion

This implementation plan extends the existing email integration infrastructure to support comprehensive outbound notifications across all modules. By reusing the EmailConfig model and SMTP logic, we minimize new infrastructure while maximizing business value.

Key Benefits: - 🚀 Automated business communications - 📧 Professional email templates - 📊 Complete email audit trail - ⚙️ User-controlled preferences - 🔄 Reliable retry mechanism

Recommendation: PROCEED WITH IMPLEMENTATION


Document Version: 1.0
Last Updated: December 31, 2024
Next Review: After Phase 3 completion