Skip to content

Add recipient list model and API for client-scoped course groups #58

@alexeygrigorev

Description

@alexeygrigorev

Context

CMP needs Datamailer-managed recipient lists for course-derived groups:

  • Everyone who registered for a course campaign.
  • Everyone enrolled in a course.
  • Everyone who submitted a homework.
  • Everyone who submitted a project.
  • Later: pending peer reviewers, certificate-eligible learners, graduates.

We should not overload tags for these lists. Tags are audience-scoped and broad; recipient lists need client/audience scope, membership audit, backfills, counters, and reconciliation state.

Proposed model

RecipientList:

  • client
  • audience
  • key, unique with client and audience, for example homework-submitters:ml-zoomcamp-2026:homework-1
  • type, for example homework_submitters, project_submitters, course_registrants
  • name
  • metadata
  • counters: member_count, active_member_count
  • last_reconciled_at
  • timestamps

RecipientListMember:

  • recipient_list
  • contact
  • email_snapshot
  • source_object_key, for example homework-submission:123
  • metadata
  • active
  • removed_at
  • timestamps

Required uniqueness:

  • (client, audience, key)
  • (recipient_list, source_object_key)
  • (recipient_list, contact)

API shape

  • PUT /api/recipient-lists/{key}
  • GET /api/recipient-lists/{key}
  • PUT /api/recipient-lists/{key}/members/{source_object_key}
  • POST /api/recipient-lists/{key}/members/bulk-upsert
  • POST /api/recipient-lists/{key}/reconcile

The member upsert should create the parent list when needed, using list metadata supplied by the request.

Acceptance criteria

  • Client API can create/update a recipient list idempotently.
  • Client API can upsert one member by source_object_key idempotently.
  • Client API can bulk upsert members.
  • Reconcile endpoint accepts a full source snapshot, supports dry_run, and can soft-remove absent members with remove_absent=true.
  • List responses include active/removed counts and last reconciliation status.
  • Tests cover client scoping: two clients can use the same list key without conflict.
  • Tests cover duplicate contact/source keys and soft removal.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    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