Newsletters

django-email-learning includes a built-in newsletter system. Platform admins can create newsletters per organisation, grow a subscriber list, and schedule email campaigns (sendouts).

Overview

  • Newsletter — a named mailing list belonging to an organisation with a configured language.

  • Subscriber — a person who has opted in via the public subscription form or been auto-subscribed through a course enrollment.

  • Sendout — a scheduled email campaign with a subject, rich-text body, and optional inline images.

  • SendoutDelivery — a per-subscriber delivery record used to track status and retries independently for each recipient.

Managing newsletters

From the platform UI, navigate to an organisation and open the Newsletters tab. You can:

  • Create and edit newsletters (title, language).

  • Link a newsletter to a course — learners who enroll in that course are automatically subscribed.

  • View and export the subscriber list as CSV.

  • Create, edit, and delete scheduled sendouts.

Public subscription

Each organisation has a public page that includes a subscription form for every newsletter that belongs to it. Visitors can subscribe without needing a platform account.

Every email sent to subscribers contains a personalised unsubscribe link. Clicking it unsubscribes the recipient immediately without requiring a login.

Sending newsletters

Sendouts are delivered by the send_newsletters management command (see Management Commands) or the matching HTTP trigger endpoint. The command fan-outs each sendout to all current subscribers, tracks delivery per subscriber, and retries failed deliveries up to the configured limit.

Configuration

All newsletter settings are nested under DJANGO_EMAIL_LEARNING in your Django settings:

DJANGO_EMAIL_LEARNING = {
    # Sender address for newsletter emails.
    # Falls back to FROM_EMAIL, then "webmaster@localhost".
    "NEWSLETTERS": {
        "FROM_EMAIL": "newsletter@example.com",
        "MAX_RETRIES": 3,       # per-subscriber retry limit (default 3)
        "MAX_SUBSCRIBERS": 500, # subscriber cap per newsletter (default: no limit)
    },

    # Optional: replace the default database-backed sendout queue.
    "SENDOUT_QUEUE": "myapp.queues.CustomSendoutQueue",

    # Base URL used to build the unsubscribe link in emails.
    "SITE_BASE_URL": "https://example.com",
}

MAX_SUBSCRIBERS sets a global cap applied to all newsletters. If you need a different limit per newsletter or per organisation, you can subclass the subscription view and override get_max_subscribers():

from django_email_learning.public.views import NewsletterSubscribeView

class MySubscribeView(NewsletterSubscribeView):
    def get_max_subscribers(self) -> int:
        # e.g. look up a per-newsletter quota from your own model
        return self.newsletter.custom_limit

The same hook exists on the public API subscription view (django_email_learning.public.api.views) if you are using the API endpoint instead.

Failure handling

The delivery job tracks each subscriber independently. If a subset of deliveries fail (e.g. due to a bad address), those are retried on subsequent runs up to MAX_RETRIES times. The sendout is still marked Sent as long as at least one subscriber received it (best-effort completion).

If every delivery permanently fails — which typically indicates an email configuration problem rather than individual bad addresses — the sendout is kept in Scheduled state, all failed deliveries are reset to pending, and a sendout_all_deliveries_failed metric and ERROR log entry are emitted so the issue is visible to operators.