11 — Deliver existing engine-owned notifications idempotently per recipient
## Objective Move existing engine-caused notifications to durable consumers while preserving recipient/suppression rules and preventing duplicate logical sends across partial retry. ## Code and evidence app/controllers/proposals_controller.rb# create/update engine-backed branches; app/services/bias/formal_proposal_registrar.rb; app/jobs/proposal_notification_job.rb; config/initializers/dual_mail_provider.rb. ProposalNotificationJob computes recipients then deliver_later per email. skip_notifications and initiating user are request-derived. Production DualMailProvider routes via SMTP (randomized provider choice); SMTP and stable Message-ID alone cannot guarantee duplicate-free delivery after accepted mail/local ack loss. Manual formal bias assignment is documented as no SMS; do not add SMS behavior. ## Changes - Limit migration to existing notifications attributable to engine-backed flows from task 01. Preserve ordinary non-engine notification behavior; no new notification categories or recipients. - Extract existing recipient selection and policy into shared code, retaining public/not_started/recommendation checks, municipality test mode, user notification preferences, secretary recipients, deduplication, suppression and existing update throttling where applicable. - Persist initiating context/suppression with accepted work. Freeze intended recipient set before the first send; maintain one delivery identity and status per stable recipient. Retries cannot resend successful recipients or dynamically grow the audience mid-delivery. - Establish a provider-backed idempotent send/lookup contract for engine-owned messages, or an explicit approved limitation. Persist provider choice across retries; do not randomly switch providers for the same logical message. Passing a Message-ID alone is not proof. - Child mail jobs must carry delivery identity and acknowledge actual send/reconciled outcome; enqueue success is not delivery completion. Handle deleted proposals/users and changed privacy safely without leaking content; record explicit not-applicable/blocked outcome. - Never include addresses/content/secret provider responses in broad operations diagnostics or high-cardinality metrics. Retain necessary protected delivery context and use existing mailer rendering. ## Acceptance and tests Extend spec/jobs/proposal_notification_job_spec.rb and relevant mailer/controller specs; proposed spec/services/voting_engine/outbox/notification_consumer_spec.rb. Test original recipient rules/test mode/suppression, no notification on rollback, missing enqueue, frozen recipients, partial recipient failure, duplicate/replayed jobs, provider stability, remote success/local ack loss, privacy changes and no added SMS. Paired provider test proves one logical delivery when strict acceptance is required. Run bundle exec rspec spec/jobs/proposal_notification_job_spec.rb spec/mailers/proposal_notification_mailer_spec.rb spec/controllers/proposals_controller_spec.rb spec/services/voting_engine/outbox/notification_consumer_spec.rb; RuboCop changed Ruby. ## Depends on https://task.matda.me/tasks/tsk_2qravp6n9b — notification/context contract. https://task.matda.me/tasks/tsk_beu5nmfqfj — worker/ledger. https://task.matda.me/tasks/tsk_5fdrm4pptn — transaction-safe ownership. ## Questions - Must proposal email satisfy strict duplicate-free remote delivery? If yes, which approved provider API supports idempotency and lookup, and is migration of these engine-owned messages away from plain SMTP authorized? - If no such provider contract is available, is an explicit at-least-once/ambiguous-reconciliation exception approved? Current source acceptance does not grant it; do not hide the guarantee gap. - If recipient policy during a prolonged outage must differ from frozen original intent plus current privacy safety checks, obtain that policy explicitly; do not invent new audiences.
Checklist
0 of 0 completeNo checklist items.
- Area
- Kaukus
- Kind
- Feature
- Priority
- Medium
- Task ID
tsk_zfh3udqh57- Updated