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¶
- Create Models:
EmailTemplatemodelEmailHistorymodel-
Migrations
-
Create Default Templates:
- Commission Note template
- Purchase Order template
- Stock Alert template
- Invoice Due template
-
Bill Due template
-
Template Rendering:
- Jinja2 or Django template engine
- Context variables for each template type
- HTML + Plain text versions
Frontend¶
- Email Template Management UI:
- List templates
- Edit template (WYSIWYG editor)
- Preview with sample data
- Available placeholders guide
Phase 3B: Notification Service (2-3 hours)¶
Backend¶
- Create Service Layer:
EmailNotificationServiceclass- Send methods for each notification type
- PDF generation integration
-
SMTP sending via existing EmailConfig
-
PDF Generation:
- Reuse existing print templates
- Generate PDF from HTML
-
Attach to email
-
Error Handling:
- Retry logic (3 attempts)
- Error logging
- Fallback to admin notification
Phase 3C: Event Triggers (2-3 hours)¶
Backend¶
- Django Signals:
- Commission note approval → Email
- PO confirmation → Email
-
Stock move completion → Check alert
-
Add Fields to Models:
PurchaseOrder.email_sent(Boolean)Product.minimum_stock_level(Decimal)-
Product.stock_alert_enabled(Boolean) -
Celery Scheduled Tasks:
- Daily task for invoice due reminders
- Daily task for bill due reminders
- Configure Celery Beat schedule
Phase 3D: Frontend UI (1-2 hours)¶
Frontend¶
- Email History Page:
- Table view of sent emails
- Filter by template type, status, date
- View email content
-
Resend failed emails
-
Notification Preferences:
- Per-user email preferences
- Unsubscribe from certain notifications
-
Email frequency settings
-
Email Preview:
- Preview email before sending
- 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¶
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 templatesPOST /core/email-templates/- Create templatePUT /core/email-templates/{id}/- Update templateGET /core/email-templates/{id}/preview/- Preview with sample data
Email History¶
GET /core/email-history/- List sent emailsGET /core/email-history/{id}/- View email detailsPOST /core/email-history/{id}/resend/- Resend failed email
Manual Triggers¶
POST /purchases/purchase-orders/{id}/send_email/- Manually send PO emailPOST /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¶
- Template rendering with various contexts
- Email sending logic
- Recipient resolution
- PDF attachment generation
- Error handling and retries
Integration Tests¶
- End-to-end commission note flow
- End-to-end PO confirmation flow
- Stock alert triggering
- Due date reminder scheduling
Manual Testing¶
- Configure SMTP for each module
- Trigger each notification type
- Verify email received
- Check email history
- 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¶
- Email Service Integration: SendGrid, Mailgun, AWS SES
- Email Tracking: Open rates, click tracking
- A/B Testing: Test different email templates
- Scheduled Sending: Send at specific times
- Email Campaigns: Bulk email to customers
- SMS Notifications: Extend to SMS for critical alerts
- 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