--- phase: quick-002 plan: 01 subsystem: notifications tags: [mentor-notifications, duplicate-prevention, post-meta, bug-fix] requires: [05-01, 05-03] provides: - Duplicate notification prevention using post meta - One-time mentor notification guarantee per job affects: [] tech-stack: added: [] patterns: - "Post meta flags for one-time event tracking" - "Idempotent notification scheduling" key-files: created: [] modified: - includes/class-notifications.php decisions: - key: "Use post meta `_ddhh_mentors_notified` for duplicate prevention" context: "Need to track whether mentors have been notified for a specific job" options: ["Post meta flag", "Custom database table", "Transient cache"] chosen: "Post meta flag" rationale: "Simple, reliable, persists forever, underscore-prefix hides from UI" - key: "Set meta flag after scheduling, not after emails sent" context: "When to mark job as notified" options: ["After scheduling batch", "After all emails sent", "Before scheduling"] chosen: "After scheduling batch" rationale: "Scheduling is synchronous and reliable; email sending is async and could fail individually" metrics: duration: 2 completed: 2026-01-29 --- # Quick Task 002: Fix Duplicate Mentor Notifications **One-liner:** Post meta guard prevents mentor re-notification on job republish after edits ## What Was Built Fixed a bug where mentors received duplicate "new job" notifications when a job was edited and republished. **The Problem:** The job lifecycle includes a status reset to `pending` after provider edits (decision 03-02). This means: 1. Provider submits → pending 2. Admin publishes → pending to publish → mentors notified ✓ 3. Provider edits → status reset to pending 4. Admin republishes → pending to publish → mentors notified AGAIN ✗ The existing guard `if ( 'publish' === $old_status )` only prevented publish→publish transitions, not pending→publish after edits. **The Solution:** Added a post meta flag `_ddhh_mentors_notified` that: - Is checked before scheduling notifications (early return if already set) - Is set after successful batch scheduling - Persists for the lifetime of the job post - Uses underscore prefix to hide from WordPress custom fields UI ## Technical Implementation **Modified:** `includes/class-notifications.php` 1. **Pre-scheduling guard** (after line 439): - `get_post_meta( $post->ID, '_ddhh_mentors_notified', true )` - If value is `'1'`, log skip message and return early - Prevents duplicate scheduling entirely 2. **Post-scheduling flag** (after line 469): - `update_post_meta( $post->ID, '_ddhh_mentors_notified', '1' )` - Sets flag after `schedule_mentor_notification_batch()` succeeds - Ensures future republish attempts skip notification **Why after scheduling, not after sending?** - Scheduling is synchronous and happens immediately - Email sending is async (Action Scheduler batches) - Individual emails might fail, but we still don't want re-notifications - The intent to notify has been recorded, which is what matters ## Verification **Syntax Check:** ```bash php -l includes/class-notifications.php # No syntax errors detected ``` **Code Review:** - ✅ `get_post_meta` check exists before mentor query - ✅ `update_post_meta` exists after scheduling - ✅ No other notification methods modified - ✅ Existing publish→publish guard preserved - ✅ Underscore-prefixed meta key (hidden from UI) ## Job Lifecycle Flow (After Fix) 1. **Initial submission** (pending): - No notification (job not published yet) - `_ddhh_mentors_notified` = not set 2. **First publish** (pending → publish): - ✅ Mentors notified - `_ddhh_mentors_notified` = '1' 3. **Provider edits** (publish → pending): - No notification (deactivation) - `_ddhh_mentors_notified` = still '1' 4. **Republish after edit** (pending → publish): - ❌ Mentors NOT notified (meta flag prevents it) - `_ddhh_mentors_notified` = still '1' 5. **Any future republish**: - ❌ Mentors NOT notified (meta flag persists) ## Impact **Fixed:** - Mentors no longer receive duplicate notifications for the same job - Reduces notification fatigue and confusion - Maintains trust in the notification system **Unchanged:** - Admin notifications for edits (still sent, as intended) - First-time publish notifications (still sent) - Publish→publish guard (still prevents updates to published jobs) - Mentor opt-in/opt-out functionality **Edge Cases Handled:** - Job edited multiple times: Only first publish notifies - Job unpublished and republished: Only first publish notifies - Job trashed and restored: Meta persists, no re-notification ## Deviations from Plan None - plan executed exactly as written. ## Testing Notes **To verify in production:** 1. Create test job, submit as provider → status: pending 2. Admin publishes → Check Action Scheduler for mentor notification batches 3. Provider edits job → status: pending 4. Admin republishes → Check Action Scheduler - should see NO new batches 5. Check error_log for: "Skipping mentor notification for job #X - mentors already notified on initial publish" **Manual database check:** ```sql SELECT post_id, meta_value FROM wp_postmeta WHERE meta_key = '_ddhh_mentors_notified'; ``` Should show '1' for all published jobs that have triggered notifications. ## Next Phase Readiness **Blockers:** None **Dependencies satisfied:** Builds on existing notification system (05-01, 05-03) **Follow-up needed:** None - fix is complete and self-contained This was a targeted bug fix with no architectural implications. The notification system is now fully idempotent - each job triggers exactly one mentor notification batch in its lifetime.