DataTalksClub / DataTalksClub/website
Extend account email preferences for event and newsletter delivery
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 0
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
Parent product decision: #22 (resolved)
Relay ownership authority: #21
Product outcome
Extend the website-owned account email-preference mechanism with two separate optional categories:
- event email for a recipient actively registered for the relevant event; and
- marketing/newsletter email.
Preferences are enforced before Relay submission. The website does not introduce a second preference store, route new work through Datamailer or Amazon SES, or add a Slack preference toggle.
Normative authority
_docs/specs/01-platform-architecture.md: account identity, after-commit durable work, and Relay ownership._docs/specs/05-events-registration-email.md: event preference, newsletter audience/import, unsubscribe, Slack,EmailDelivery, and Relay worker contracts._docs/specs/07-security-privacy-operations.md: minimization, redaction, consent evidence, retention, and protected-data handling._docs/specs/09-migration-rollout-roadmap.md: send-disabled imports, exact Relay deployment gates, one active sender per purpose, and rollback holds._docs/specs/10-verification-strategy.md: token, preference, delivery, migration, browser, and network-negative evidence._docs/PROCESS.md: dependency ordering and engineer -> independent tester -> PM -> merge/on-call lifecycle.
Product contract
Preference categories
- Event email is optional and applies only to a recipient registered for the relevant event. Turning it off suppresses verification, confirmation, cancellation, reschedule, reminder, calendar invitation, and every other event-lifecycle message. Event preference is separate from newsletter/marketing preference.
- Marketing/newsletter is a separate optional category. New member/account signup defaults it on, but an unverified new account is not a newsletter recipient. The recipient can unsubscribe later.
- Slack access is a one-time requested-service message, not a recurring preference category. It has no preference toggle and is not sent again automatically. Account verification and password recovery are also non-optional security messages.
Audiences and onboarding
- A new account receives email verification first. After successful verification, the Slack-owned onboarding flow may create one Slack join-link delivery; the current link is also available in the authenticated member area.
- Legacy/imported contacts bypass the new-account verification-to-Slack sequence and do not receive an automatic Slack message merely because they were imported.
- Newsletter delivery targets verified contacts plus the explicit legacy/imported audience, which is treated as assumed verified for newsletter delivery. A new account must verify before entering the verified audience.
- An identifiable logged-in interaction or a recipient-specific newsletter-link interaction may move a legacy contact from
legacytoverified. This is not a newsletter delivery prerequisite and does not trigger another email. A bare anonymous visit does not identify a contact and does not change status.
Mailchimp reconciliation
Mailchimp is authoritative for legacy marketing subscription state. Provide two separate, idempotent, send-disabled import operations:
- subscribed list: turn the matched legacy marketing preference on;
- unsubscribed list: turn the matched marketing preference off.
The operations affect only the marketing/newsletter preference. A contact absent from both lists keeps the current platform preference. The supported input lists are assumed clean and mutually exclusive; overlap precedence is out of scope. Import or replay never queues or sends email.
Unsubscribe and link safety
- A visible body/footer unsubscribe link opens the website preference page. The recipient unticks unwanted categories and submits; the initial GET is non-mutating.
- A supported email-header one-click action may immediately and idempotently disable only the optional category represented by that message, without changing unrelated categories.
- Preference and newsletter-attribution links use opaque, recipient- and purpose-scoped, expiring, replay-safe tokens. Raw email addresses never appear in URLs or logs. A newsletter token is for recipient attribution and legacy-status promotion, not an additional verification gate.
Scope
- Replace the legacy Datamailer-backed account preference adapter with a website-owned application service and canonical persisted preference state.
- Expose event and marketing/newsletter controls through the existing authenticated account-settings experience; preserve existing course categories during migration.
- Apply the same service from HTML, API, imports, business triggers, and Relay submission checks.
- Implement safe preference-center body links, category-specific email-header one-click handling, and recipient-attribution tokens.
- Add deterministic, idempotent, send-disabled subscribed and unsubscribed Mailchimp import commands with counts/checksums/conflict reporting and no protected values in routine output.
- Integrate with the accepted event-registration and ordinary
EmailDeliveryinterfaces; do not invent either contract here.
Acceptance criteria
- Existing account settings exposes event and marketing/newsletter preferences from one website-owned service/store; existing course categories retain their values and behavior, and no preference read/write calls Datamailer.
- Event mail is eligible only for an active registrant for the relevant event and only while event preference is enabled. Opt-out suppresses every event-lifecycle delivery intent, including verification and calendar messages.
- Marketing/newsletter delivery is eligible only for verified contacts and the explicit legacy audience; unverified new accounts are excluded.
- Preference state is checked atomically before the Relay submission path, with idempotent suppression and no mutation of unrelated categories.
- A body/footer link opens the preference page without mutating on GET; an authenticated or valid token-authorized submit changes only selected optional categories.
- The supported email-header one-click action disables only the represented optional category immediately and idempotently; invalid, expired, wrong-purpose, replayed, and near-match requests fail safely without enumeration.
- No Slack preference exists. The accepted onboarding path remains verification first, then at most one automatic Slack delivery plus authenticated reveal; retries and legacy imports create no automatic duplicate.
- Identifiable account/newsletter interactions may promote
legacytoverified; anonymous, shared, malformed, and un-attributed visits never do. - Subscribed and unsubscribed Mailchimp imports independently turn the legacy marketing preference on/off; absence preserves current state; apply-twice is stable and import never creates delivery intent, job, callback, or provider request.
- Tokens are opaque, recipient- and purpose-scoped, expiring, replay-safe, revocable with the owning identity/preference revision, redacted, and absent from URLs in logs, analytics, audits, screenshots, and errors.
- Email mutations use owning application services and the accepted single
EmailDeliveryplus after-commit durable-job/Relay model; no new Datamailer, direct SES, local renderer, or provider-event path exists. - Focused Django, migration, security/redaction, API parity, and graph-selected browser tests pass through the current versioned verification plan and independent tester gate.
Browser and integration scenarios
At desktop about 1440x900 and mobile about 390x844:
- An authenticated member reads and changes event and marketing preferences; focus, errors, stale submission, refresh, and back navigation preserve a coherent accessible state and never change unrelated course preferences.
- A valid body-link token opens the preference center without mutation; submit changes only the chosen category. Invalid, expired, revoked, wrong-purpose, replayed, and another-recipient tokens produce uniform safe outcomes without leaking identity.
- The email-header one-click contract disables exactly one optional category without a page visit; repeated requests are idempotent and non-optional messages expose no such action.
- Event registration with event mail enabled/disabled exercises the accepted event and delivery fakes and proves intent creation/suppression. No test performs a live Relay, Datamailer, SES, Mailchimp, or provider call.
- Verified, unverified-new, and explicit legacy newsletter audiences are distinguished; an attributable legacy interaction may promote status while an anonymous/shared link cannot.
- Authorized import preview/apply and denial/error states expose bounded counts and safe reason codes only, never raw addresses, tokens, provider payloads, or message bodies.
Dependencies and sequencing
- Product decisions are complete in #22 and the authoritative specifications; no new preference or Slack decision is requested here.
- Website implementation that creates or suppresses delivery work depends on accepted #49 ordinary
EmailDelivery/after-commit Relay interfaces. Event-specific enforcement additionally depends on the accepted native registration interface owned by #46. Slack sequencing is owned by #108's bounded Slack child (#249); #227 only preserves the no-toggle/no-duplicate boundary. - Any real Relay consumer/send path remains disabled until Relay #1, #2, and #3 are accepted and deployed at an exact pinned commit/OpenAPI version, with website consumer evidence. Relay main
69189fb2911c4b808dd7d4e5913da91d7ded6fa1adds only aCLAUDE.mdpointer over the audited baseline; all three Relay contract issues remain open. - #50 consumes this recipient/preference contract when wiring purposes and retiring Datamailer. It is coordinated downstream work, not a prerequisite for defining preferences; do not create a #227 <-> #50 dependency cycle.
- #225 is a separate anonymous browser analytics-consent surface and is not the account email-preference foundation.
- Current website
origin/mainface8e4808d65afbf0374d1ced7a88079950d663still has a deferredemail_app, no nativeEventRegistration, and an account preference endpoint backed by Datamailer. Therefore this full issue is dependency-blocked before engineering. A narrower source-only foundation would require its own raw intake and PM-groomed child with an interface that cannot pre-empt #49/#46/#249; none exists today.
Non-goals
- No Slack preference toggle, Slack API/SCIM work, join-secret storage, or independent Slack onboarding implementation.
- No production Relay, Mailchimp, Datamailer, SES, credential, sender/domain, callback, deployment, protected-data import, or broad-recipient action.
- No second preference store/UI, local email renderer/template catalog, website provider-attempt/event stack, or synchronous network side effect.
- No marketing campaign composer, engagement/open/click tracking, analytics consent changes, or overlapping-Mailchimp-list precedence rule.
- No event-registration model or generic delivery lifecycle invented inside this issue.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the listed _docs/specs files and _docs/PROCESS.md, then inspect the deferred email_app and the current account preference endpoint on origin/main. Confirm the accepted #49 and #46 interfaces and the Relay deployment gates before implementation. Done means the acceptance criteria and focused Django, migration, security/redaction, API, and browser tests pass without provider calls.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, backend, databases, frontend, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 20/100