Skip to 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="[email protected]" )

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="[email protected]", 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": "[email protected]" } # 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 protected] # 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 = "[email protected]" newsletter_reply_to: str = "[email protected]" 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="[email protected]" ) 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]