Original ticket
Add notification preferences. Users should be able to turn emails and push notifications on or off. Make sure we do not spam people.
This case shows how a broad "let users manage notifications" request becomes a bounded packet with consent rules, existing-user migration, duplicate-send evidence, and AI coding guardrails.
Add notification preferences. Users should be able to turn emails and push notifications on or off. Make sure we do not spam people.
Feature: Notification preference migration Owner: Lifecycle Messaging Status: Ready for review Context: - Existing users have email_opt_out in users. - Push tokens live in device_tokens. - Marketing emails already respect unsubscribe records. Goal: - Add one preference model for product email and push. - Preserve all existing opt-outs during migration. - Prevent duplicate sends while old and new readers coexist. Non-goals: - No SMS preferences. - No marketing email redesign. - No bulk admin editor. - No new notification history page.
Existing opt-outs stay off. Users without an explicit opt-out keep their current delivery behavior until they change settings.
Old and new preference readers may coexist for one release, but delivery must choose one source of truth for each notification.
This change owns product email and push only. Marketing unsubscribe rules and SMS consent stay outside the diff.
Preference categories: - product_updates_email - product_updates_push - billing_alert_email - billing_alert_push Rules: - Opt-out records migrate as disabled preferences. - Billing alerts cannot be disabled in this release. - Push disabled means no new push jobs are enqueued. - Email disabled means delivery worker suppresses send.
- [ ] Add preference table and backfill script. - [ ] Add read API for settings UI. - [ ] Add update API with audit fields. - [ ] Update email worker suppression. - [ ] Update push enqueue suppression. - [ ] Add dual-read safety tests.
- Given an existing email opt-out When preferences are migrated Then product_updates_email is disabled. - Given a user disables push When an event triggers a product update Then no push job is enqueued for that user.
Required: - backfill dry-run count - opt-out preservation test - duplicate-send regression test - worker suppression test - settings UI screenshot - rollback plan for preference reader flag
The first review question is whether the spec preserves current consent. A settings UI is easy to generate, but the dangerous work is migration: existing opt-outs, device tokens, marketing unsubscribe records, and product alert rules all look similar until the spec gives each one an owner.
The second review question is duplicate delivery. During a migration, old and new readers often run side by side. If both enqueue messages, users may receive two notifications for the same event. A good packet names the dual-read window, the source of truth, and the regression test that proves only one job is created.
The third review question is rollback. Turning off the UI does not undo migrated preference rows. The packet should name the flag that controls the new reader and the query that verifies migrated opt-outs remain preserved if the release is paused.
Reject SMS, marketing redesign, notification history, and admin tooling unless they have their own spec. They are tempting but separate products.
Require a dry-run count, a migrated opt-out fixture, a duplicate-send regression test, and one UI screenshot that matches API state.
Release should happen behind a reader flag. The stop signal is any duplicate product notification or mismatch between preference API and worker decision.
Implement the notification preference migration only. Allowed areas: - preference schema and migration - settings API read/update - email worker suppression - push enqueue suppression - tests and fixtures for the acceptance criteria Do not add: - SMS consent - notification history - admin bulk editor - marketing email redesign - unrelated settings page refactor Every changed file must map to one task and one acceptance criterion.
This guardrail is deliberately strict. Notification work invites adjacent improvements: new channels, a nicer settings page, admin overrides, campaign history, and analytics dashboards. Those may be valuable, but adding them inside this migration makes review weaker and consent behavior harder to audit.
Use this pattern whenever a preference, consent, or settings change affects delivery behavior. Replace the channel names with your own, then write the existing source of truth before the new model. If the current system has unsubscribe rows, device tokens, profile flags, team-level settings, or regional consent rules, list each one explicitly. Do not let the new UI become the only thing the spec describes.
For smaller teams, the packet can stay short. The essential parts are still the same: current behavior, migration rule, dual-read rule, non-goals, and evidence. If any of those are missing, the AI implementation may look polished while changing consent semantics in a way nobody reviewed.
For larger teams, add one more section: ownership. Product messaging, lifecycle marketing, billing alerts, and mobile push often have different owners. A spec that fails to name them may pass engineering review while violating another team's delivery policy.
The worker must enforce preferences. A settings toggle that is not checked during delivery only creates false confidence.
Changing default delivery for existing users is a product and consent decision. It must be visible in the spec.
A migration without dry-run counts and mismatch checks cannot prove it preserved current opt-outs.
Notification preference migrations often fail after the UI looks correct, because delivery workers and queued jobs are where user trust is actually protected.
During one release window, watch whether old and new readers enqueue the same event. A duplicate job is a stop signal.
Sample users who opted out before migration and verify the preference row, API response, and worker decision all agree.
If the preference work requires table changes, pair this with the database schema case; if scope is still broad, start with the vague rewrite examples.
Start with the packet generator, then paste the migration rules and non-goals into the implementation prompt before any AI coding session.
This case is a teaching scenario based on common notification migration failures: consent drift, duplicate sends, missing backfill evidence, and UI-only preference checks.