Skip to content

Send transactional email to a recipient list with one client trigger #59

@alexeygrigorev

Description

@alexeygrigorev

Context

CMP wants to trigger score and reminder emails with one Datamailer API call, instead of one call per learner. Datamailer should expand a stored recipient list, create per-recipient transactional messages idempotently, and enqueue delivery asynchronously.

This depends on recipient lists.

Desired API

POST /api/recipient-lists/{key}/send

Example request:

{
  "send_key": "homework-score:ml-zoomcamp-2026:homework-1",
  "template_key": "homework-score-published",
  "from_email": "courses",
  "shared_context": {
    "course_title": "ML Zoomcamp 2026",
    "homework_title": "Homework 1"
  },
  "metadata": {
    "source": "course-management-platform",
    "event": "homework_score_published"
  }
}

If an email needs per-recipient context such as a score, support one of these designs:

  • A bulk request that includes per-recipient context keyed by source_object_key.
  • An asynchronous context-provider callback to CMP that Datamailer workers call in chunks.

Acceptance criteria

  • send_key is idempotent per client: replay returns the existing send/batch result.
  • Datamailer snapshots active list members when the send is triggered.
  • Datamailer creates one transactional message per eligible member with stable per-recipient idempotency.
  • Hard bounced and complained contacts are skipped/suppressed.
  • Pending/unsubscribed contacts are skipped when the send type requires marketing/list consent.
  • The API returns quickly after enqueueing work; it does not wait for all emails to send.
  • Operator/API views expose counts: intended, queued, skipped, failed, sent.
  • Tests cover replay, suppressed recipients, removed list members, and missing templates.

Related

Depends on recipient list model/API.
Blocks CMP score notifications that need one trigger call.

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