Skip to content

[Backend Integration] Mailing List Auto Subscription #2282

Description

@henryajisegiri

[Backend Integration] Mailing List Auto-Subscription

The definitions in this ticket were based off of the discussions in the comments and a Slack thread.

Goal

Build the Django-side integration layer for Boost mailing list subscriptions — covering the subscribe/unsubscribe client, confirmation flow, subscription tracking model, and the mailing list card component wired to live endpoints.


Scope

Full-stack: mailman-core REST API client, confirmation email flow, subscription model, HTMX-powered subscribe card, and confirmation pages.


Acceptance Criteria

Mailman REST API Client

  • A thin mailing_list/client.py wrapper around mailman-core's REST API exposes three operations:
    • subscribe(email, list_id) — POSTs to /3.1/members with all pre-confirmation flags set to True, treating a 409 (already a member) as a no-op.
    • unsubscribe(email, list_id) — looks up the member ID then DELETEs /3.1/members/<id>; if the member is not found, discards any pending subscription request so the user can re-subscribe cleanly.
    • is_confirmed(email, list_id) — GETs /3.1/lists/<list_id>/member/<email> to check active membership.
  • All three raise MailmanAPIError on API failure; callers handle errors gracefully.
  • API credentials (MAILMAN_REST_API_URL, MAILMAN_REST_API_PATH, MAILMAN_REST_API_USER, MAILMAN_REST_API_PASS) and the list of managed lists (MAILMAN_LISTS) are read from Django settings / environment variables.

Subscription Model

  • New UserMailingListSubscription model tracks boost.org-managed subscriptions:
    • user (FK to AUTH_USER_MODEL), list_id, email, status (pending | active), subscribed_at.
    • unique_together on (user, list_id).

Mailing List Labels

  • mailing_list/constants.py defines human-readable labels, addresses, and descriptions for the 3 active lists:
    • boost.lists.boost.org → "Boost Developers"
    • boost-announce.lists.boost.org → "Boost Announcements"
    • boost-users.lists.boost.org → "Boost Users"

Subscription Views & Confirmation Flow

  • QuickSubscribeView (POST /mailing-list/quick-subscribe/) — single-list subscribe for both authenticated and anonymous users, returning an HTMX outerHTML swap of the mailing list card:
    • Authenticated path: creates a UserMailingListSubscription with status=pending, sends a confirmation email, and renders a pending state. Re-POSTing for an already-pending subscription returns the pending card without re-sending. Already-active subscriptions return the success card.
    • Anonymous path: stateless — checks Mailman for an existing subscription, sends a confirmation email, and renders a pending card.
    • Returns inline error states (invalid email, invalid list ID, email send failure) as re-renders of the same card partial.
  • SubscribeView (POST /mailing-list/subscribe/) — authenticated, multi-list subscribe/unsubscribe via the user profile page:
    • Computes the delta between currently-subscribed lists and the submitted set.
    • Creates UserMailingListSubscription(status=pending) records and sends a single confirmation email covering all newly-selected lists.
    • Unsubscribes from deselected lists (calls mailman_unsubscribe; pending records are simply deleted without an API call).
    • Returns an HTMX fragment (_subscribe_result.html) listing pending, subscribed, unsubscribed, and failed lists.
  • ConfirmSubscriptionView (GET /mailing-list/confirm/<token>/) — validates a signed token (Django signing, defaults to 7-day TTL), calls mailman_subscribe for each list in the payload, and updates matching UserMailingListSubscription records to status=active. Renders either confirm_success.html or confirm_invalid.html.
  • Confirmation email (confirm_subscription.txt) includes the list name, address, description, the signed confirmation URL, and the expiry window.

Mailing List Card Component

  • _mailing_list_card.html partial is fully wired up with three states:
    • Default (subscribe form): HTMX POST to QuickSubscribeView with outerHTML swap; renders inline field errors.
    • Pending: shows the email address a confirmation was sent to and a "Manage your lists →" link.
    • Subscribed: swaps to _subscribe_success_card.html showing the subscribed email, the count of active subscriptions, and a "Manage your lists →" link.
  • MailingListCardMixin (in mailing_list/mixins.py) injects all required context variables into any class-based view that includes the card:
    • mailing_list_card_subscribe_url, mailing_list_card_list_id, mailing_list_card_login_url
    • mailing_list_card_is_subscribed, mailing_list_card_state, mailing_list_card_user_email, mailing_list_card_manage_url, mailing_list_card_subscription_count (authenticated users only)
  • CommunityView and LearnPageView in core/views.py both inherit MailingListCardMixin.

Confirmation Pages

  • confirm_success.html — lists confirmed and (if any) failed lists by name and address; links back to the homepage.
  • confirm_invalid.html — shown for expired or tampered tokens; includes the expiry window and a link to the homepage.

FE/BE Actions

Description Endpoint
Subscribe (single list, authenticated + anonymous) POST /mailing-list/quick-subscribe/
Subscribe / unsubscribe (multi-list, authenticated only) POST /mailing-list/subscribe/
Confirm email token GET /mailing-list/confirm/<token>/

Out of Scope

  • Infrastructure: exposing mailman-core REST API to boost.org's server (firewall / bind_address config) — this is a server-side task for Sam.
  • Post-auth subscription modal shown after login/signup.
  • Subscription management section in the user profile settings page (toggling lists from the profile).
  • Regular import of existing mailman subscriptions for previously subscribed users.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions