Skip to main content

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)

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]