Newsletter System
Django-CFG includes a comprehensive email newsletter system for managing email campaigns, subscriptions, and automated marketing workflows.
Overviewโ
The Newsletter app provides:
- Newsletter management with multiple campaigns
- Subscription management with opt-in/opt-out
- Email tracking (opens, clicks, bounces, unsubscribes)
- Campaign analytics and performance metrics
- Auto-subscribe new users to newsletters
- Email templates and customization
- Admin interface for campaign management
Quick Startโ
Enable Newsletter in Configurationโ
# config.py
from django_cfg import DjangoConfig
from .environment import env
class MyConfig(DjangoConfig):
enable_newsletter: bool = True # Enable newsletter system
# Email configuration required
email: EmailConfig = EmailConfig(
backend="django.core.mail.backends.smtp.EmailBackend",
host="smtp.gmail.com",
port=587,
use_tls=True,
username=env.email.username,
password=env.email.password,
default_from_email="noreply@yoursite.com"
)
Create Newsletter Campaignโ
from django_cfg.apps.newsletter.models import Newsletter, NewsletterSubscription
# Create newsletter
newsletter = Newsletter.objects.create(
title="Weekly Tech Updates",
description="Latest technology news and updates",
is_active=True,
auto_subscribe=True # Auto-subscribe new users
)
# Subscribe user
subscription = NewsletterSubscription.objects.create(
newsletter=newsletter,
email="user@example.com",
is_active=True
)
Data Modelsโ
Newsletter Modelโ
class Newsletter(models.Model):
"""Newsletter model for managing email campaigns."""
title = models.CharField(max_length=255)
description = models.TextField(blank=True)
is_active = models.BooleanField(default=True)
auto_subscribe = models.BooleanField(
default=False,
help_text="Automatically subscribe new users to this newsletter"
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
@property
def subscribers_count(self):
"""Get count of active subscribers."""
return self.subscriptions.filter(is_active=True).count()
Subscription Modelโ
class NewsletterSubscription(models.Model):
"""Newsletter subscription model."""
class SubscriptionStatus(models.TextChoices):
PENDING = 'pending', 'Pending Confirmation'
ACTIVE = 'active', 'Active'
UNSUBSCRIBED = 'unsubscribed', 'Unsubscribed'
BOUNCED = 'bounced', 'Bounced'
newsletter = models.ForeignKey(Newsletter, on_delete=models.CASCADE, related_name='subscriptions')
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
email = models.EmailField()
status = models.CharField(max_length=20, choices=SubscriptionStatus.choices, default=SubscriptionStatus.PENDING)
is_active = models.BooleanField(default=True)
# Tracking
subscribed_at = models.DateTimeField(auto_now_add=True)
unsubscribed_at = models.DateTimeField(null=True, blank=True)
confirmation_token = models.CharField(max_length=64, unique=True)
Email Campaign Modelโ
class NewsletterEmail(models.Model):
"""Individual newsletter email campaigns."""
class EmailStatus(models.TextChoices):
DRAFT = 'draft', 'Draft'
SCHEDULED = 'scheduled', 'Scheduled'
SENDING = 'sending', 'Sending'
SENT = 'sent', 'Sent'
FAILED = 'failed', 'Failed'
newsletter = models.ForeignKey(Newsletter, on_delete=models.CASCADE, related_name='emails')
subject = models.CharField(max_length=255)
content_html = models.TextField()
content_text = models.TextField(blank=True)
status = models.CharField(max_length=20, choices=EmailStatus.choices, default=EmailStatus.DRAFT)
scheduled_at = models.DateTimeField(null=True, blank=True)
sent_at = models.DateTimeField(null=True, blank=True)
# Analytics
total_sent = models.PositiveIntegerField(default=0)
total_opened = models.PositiveIntegerField(default=0)
total_clicked = models.PositiveIntegerField(default=0)
total_bounced = models.PositiveIntegerField(default=0)
total_unsubscribed = models.PositiveIntegerField(default=0)
API Usageโ
Newsletter Management APIโ
# Create newsletter subscription
POST /api/newsletter/subscribe/
{
"newsletter_id": 1,
"email": "user@example.com"
}
# Unsubscribe
POST /api/newsletter/unsubscribe/
{
"token": "unsubscribe_token_here"
}
# Get newsletter campaigns
GET /api/newsletter/campaigns/
# Send newsletter email
POST /api/newsletter/campaigns/{id}/send/
{
"subject": "Weekly Update #42",
"content_html": "<h1>Newsletter Content</h1>",
"scheduled_at": "2024-01-15T10:00:00Z"
}
Subscription Managementโ
from django_cfg.apps.newsletter.models import Newsletter, NewsletterSubscription
# Subscribe user to newsletter
def subscribe_user(email, newsletter_id):
newsletter = Newsletter.objects.get(id=newsletter_id)
subscription, created = NewsletterSubscription.objects.get_or_create(
newsletter=newsletter,
email=email,
defaults={'is_active': True}
)
return subscription
# Bulk subscribe users
def bulk_subscribe(emails, newsletter_id):
newsletter = Newsletter.objects.get(id=newsletter_id)
subscriptions = []
for email in emails:
subscription = NewsletterSubscription(
newsletter=newsletter,
email=email,
is_active=True
)
subscriptions.append(subscription)
NewsletterSubscription.objects.bulk_create(subscriptions, ignore_conflicts=True)
Email Campaignsโ
Campaign Creationโ
from django_cfg.apps.newsletter.models import NewsletterEmail
# Create email campaign
campaign = NewsletterEmail.objects.create(
newsletter=newsletter,
subject="Weekly Tech Updates - Issue #42",
content_html="""
<html>
<body>
<h1>This Week in Tech</h1>
<p>Latest updates from the tech world...</p>
<a href="{{unsubscribe_url}}">Unsubscribe</a>
</body>
</html>
""",
content_text="This Week in Tech\n\nLatest updates..."
)
# Schedule campaign
from django.utils import timezone
from datetime import timedelta
campaign.scheduled_at = timezone.now() + timedelta(hours=1)
campaign.status = NewsletterEmail.EmailStatus.SCHEDULED
campaign.save()
Email Templatesโ
# Email template with variables
template_html = """
<!DOCTYPE html>
<html>
<head>
<title>{{newsletter.title}}</title>
</head>
<body>
<h1>{{subject}}</h1>
<div>{{content}}</div>
<footer>
<p>You're receiving this because you subscribed to {{newsletter.title}}</p>
<a href="{{unsubscribe_url}}">Unsubscribe</a>
</footer>
</body>
</html>
"""
# Template context
context = {
'newsletter': newsletter,
'subject': campaign.subject,
'content': campaign.content_html,
'unsubscribe_url': f"/newsletter/unsubscribe/{subscription.confirmation_token}/"
}
Analytics & Trackingโ
Email Trackingโ
from django_cfg.apps.newsletter.models import EmailTracking
class EmailTracking(models.Model):
"""Track email interactions."""
class ActionType(models.TextChoices):
SENT = 'sent', 'Sent'
OPENED = 'opened', 'Opened'
CLICKED = 'clicked', 'Clicked'
BOUNCED = 'bounced', 'Bounced'
UNSUBSCRIBED = 'unsubscribed', 'Unsubscribed'
email = models.ForeignKey(NewsletterEmail, on_delete=models.CASCADE)
subscription = models.ForeignKey(NewsletterSubscription, on_delete=models.CASCADE)
action_type = models.CharField(max_length=20, choices=ActionType.choices)
timestamp = models.DateTimeField(auto_now_add=True)
user_agent = models.TextField(blank=True)
ip_address = models.GenericIPAddressField(null=True, blank=True)
Campaign Analyticsโ
# Campaign performance metrics
def get_campaign_stats(campaign_id):
campaign = NewsletterEmail.objects.get(id=campaign_id)
stats = {
'total_sent': campaign.total_sent,
'total_opened': campaign.total_opened,
'total_clicked': campaign.total_clicked,
'total_bounced': campaign.total_bounced,
'total_unsubscribed': campaign.total_unsubscribed,
# Calculated metrics
'open_rate': (campaign.total_opened / campaign.total_sent * 100) if campaign.total_sent > 0 else 0,
'click_rate': (campaign.total_clicked / campaign.total_sent * 100) if campaign.total_sent > 0 else 0,
'bounce_rate': (campaign.total_bounced / campaign.total_sent * 100) if campaign.total_sent > 0 else 0,
'unsubscribe_rate': (campaign.total_unsubscribed / campaign.total_sent * 100) if campaign.total_sent > 0 else 0,
}
return stats
Admin Interfaceโ
Newsletter Managementโ
The admin interface provides:
- Newsletter creation and management
- Subscriber management with bulk actions
- Campaign creation and scheduling
- Analytics dashboard with performance metrics
- Email preview and testing
- Export functionality for subscriber lists
Management Commandsโ
# Send scheduled newsletters
python manage.py send_newsletters
# Newsletter statistics
python manage.py newsletter_stats
# Clean up old tracking data
python manage.py cleanup_newsletter_tracking --days=90
# Test newsletter sending
python manage.py test_newsletter --email=test@example.com
# Export subscribers
python manage.py export_subscribers --newsletter=1 --format=csv
Integration Examplesโ
Auto-Subscribe New Usersโ
# signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth import get_user_model
from django_cfg.apps.newsletter.models import Newsletter, NewsletterSubscription
User = get_user_model()
@receiver(post_save, sender=User)
def auto_subscribe_user(sender, instance, created, **kwargs):
"""Auto-subscribe new users to newsletters with auto_subscribe=True"""
if created:
auto_newsletters = Newsletter.objects.filter(auto_subscribe=True, is_active=True)
for newsletter in auto_newsletters:
NewsletterSubscription.objects.get_or_create(
newsletter=newsletter,
user=instance,
email=instance.email,
defaults={'is_active': True}
)
Newsletter Widgetโ
# forms.py
from django import forms
from django_cfg.apps.newsletter.models import Newsletter
class NewsletterSubscriptionForm(forms.Form):
email = forms.EmailField(
widget=forms.EmailInput(attrs={
'placeholder': 'Enter your email',
'class': 'form-control'
})
)
newsletter = forms.ModelChoiceField(
queryset=Newsletter.objects.filter(is_active=True),
widget=forms.HiddenInput()
)
def save(self):
email = self.cleaned_data['email']
newsletter = self.cleaned_data['newsletter']
subscription, created = NewsletterSubscription.objects.get_or_create(
newsletter=newsletter,
email=email,
defaults={'is_active': True}
)
return subscription
Configuration Optionsโ
Newsletter Settingsโ
# config.py
class MyConfig(DjangoConfig):
enable_newsletter: bool = True
# Newsletter-specific settings
newsletter_from_email: str = "newsletter@company.com"
newsletter_reply_to: str = "support@company.com"
newsletter_unsubscribe_url: str = "https://yoursite.com/newsletter/unsubscribe/"
newsletter_tracking_enabled: bool = True
newsletter_double_opt_in: bool = True # Require email confirmation
๐งช Testingโ
Newsletter Testingโ
# tests/test_newsletter.py
from django.test import TestCase
from django_cfg.apps.newsletter.models import Newsletter, NewsletterSubscription
class NewsletterTest(TestCase):
def test_newsletter_creation(self):
newsletter = Newsletter.objects.create(
title="Test Newsletter",
description="Test description"
)
self.assertEqual(str(newsletter), "Test Newsletter")
def test_subscription_creation(self):
newsletter = Newsletter.objects.create(title="Test Newsletter")
subscription = NewsletterSubscription.objects.create(
newsletter=newsletter,
email="test@example.com"
)
self.assertEqual(subscription.status, NewsletterSubscription.SubscriptionStatus.PENDING)
def test_auto_subscribe(self):
newsletter = Newsletter.objects.create(
title="Auto Newsletter",
auto_subscribe=True
)
# Test that new users are auto-subscribed
# (implementation depends on your user creation flow)
Related Documentationโ
- Email Configuration - Email setup
- Leads System - Lead management integration
- User Management - User account system
- Task System - Background email processing
The Newsletter system provides comprehensive email marketing for your Django applications! ๐ง
TAGS: newsletter, email-marketing, campaigns, subscriptions, analytics DEPENDS_ON: [accounts, email, tasks] USED_BY: [leads, support, marketing]