# Email Notification System Audit **Date:** January 29, 2026 **Status:** ✅ System Architecture Sound, Minor Issues Identified --- ## Executive Summary The WooNooW email notification system is **well-architected** with proper async handling, template rendering, and event management. The main components work together correctly. However, some potential gaps and improvements were identified. --- ## System Architecture ```mermaid flowchart TD A[WooCommerce Hooks] --> B[EmailManager] B --> C{Is WooNooW Mode?} C -->|Yes| D[EmailRenderer] C -->|No| E[WC Default Emails] D --> F[TemplateProvider] F --> G[Get Template] G --> H[Replace Variables] H --> I[Parse Markdown/Cards] I --> J[wp_mail] J --> K[WooEmailOverride Intercepts] K --> L[MailQueue::enqueue] L --> M[Action Scheduler] M --> N[MailQueue::sendNow] N --> O[Actual wp_mail] ``` --- ## Core Components | File | Purpose | |------|---------| | [EmailManager.php](file:///Users/dwindown/Local%20Sites/woonoow/app/public/wp-content/plugins/woonoow/includes/Core/Notifications/EmailManager.php) | Hooks WC order events, disables WC emails, routes to renderer | | [EmailRenderer.php](file:///Users/dwindown/Local%20Sites/woonoow/app/public/wp-content/plugins/woonoow/includes/Core/Notifications/EmailRenderer.php) | Renders templates, replaces variables, parses markdown | | [TemplateProvider.php](file:///Users/dwindown/Local%20Sites/woonoow/app/public/wp-content/plugins/woonoow/includes/Core/Notifications/TemplateProvider.php) | Manages templates, defaults, variable definitions | | [EventRegistry.php](file:///Users/dwindown/Local%20Sites/woonoow/app/public/wp-content/plugins/woonoow/includes/Core/Notifications/EventRegistry.php) | Central registry of all notification events | | [NotificationManager.php](file:///Users/dwindown/Local%20Sites/woonoow/app/public/wp-content/plugins/woonoow/includes/Core/Notifications/NotificationManager.php) | Validates settings, dispatches to channels | | [WooEmailOverride.php](file:///Users/dwindown/Local%20Sites/woonoow/app/public/wp-content/plugins/woonoow/includes/Core/Mail/WooEmailOverride.php) | Intercepts wp_mail via `pre_wp_mail` filter | | [MailQueue.php](file:///Users/dwindown/Local%20Sites/woonoow/app/public/wp-content/plugins/woonoow/includes/Core/Mail/MailQueue.php) | Async queue via Action Scheduler | --- ## Email Flow Trace ### 1. Event Trigger - WooCommerce fires hooks like `woocommerce_order_status_pending_to_processing` - `EmailManager::init_hooks()` registers callbacks for these hooks ### 2. EmailManager Processing ```php // In EmailManager.php add_action('woocommerce_order_status_pending_to_processing', [$this, 'send_order_processing_email']); ``` - Checks if WooNooW mode enabled: `is_enabled()` - Checks if event enabled: `is_event_enabled()` - Calls `send_email($event_id, $recipient_type, $order)` ### 3. Email Rendering - `EmailRenderer::render()` called - Gets template from `TemplateProvider::get_template()` - Gets variables from `get_variables()` (order, customer, product data) - Replaces `{variable}` placeholders - Parses `[card]` markdown syntax - Wraps in HTML template from `templates/emails/base.html` ### 4. wp_mail Interception - `wp_mail()` is called with rendered HTML - `WooEmailOverride::interceptMail()` catches via `pre_wp_mail` filter - Returns `true` to short-circuit synchronous send ### 5. Queue & Async Send - `MailQueue::enqueue()` stores payload in `wp_options` (temp) - Schedules `woonoow/mail/send` action via Action Scheduler - `MailQueue::sendNow()` runs asynchronously: - Retrieves payload from options - Disables `WooEmailOverride` to prevent loop - Calls actual `wp_mail()` - Deletes temp option --- ## Findings ### ✅ Working Correctly 1. **Async Email Queue**: Properly prevents timeout issues 2. **Template System**: Variables replaced correctly 3. **Event Registry**: Single source of truth 4. **Subscription Events**: Registered via `woonoow_notification_events_registry` filter 5. **Global Toggle**: WooNooW vs WooCommerce mode works 6. **WC Email Disable**: Default emails properly disabled when WooNooW active ### ⚠️ Potential Issues #### 1. Missing Subscription Variable Population in EmailRenderer **Location:** [EmailRenderer.php:147-299](file:///Users/dwindown/Local%20Sites/woonoow/app/public/wp-content/plugins/woonoow/includes/Core/Notifications/EmailRenderer.php#L147-L299) **Issue:** `get_variables()` handles `WC_Order`, `WC_Product`, `WC_Customer` but NOT subscription objects. Subscription notifications pass data like: ```php $data = [ 'subscription' => $subscription, // Custom subscription object 'customer' => $user, 'product' => $product, ... ] ``` **Impact:** Subscription email variables like `{subscription_id}`, `{billing_period}`, `{next_payment_date}` may not be replaced. **Recommendation:** Add subscription variable population in `EmailRenderer::get_variables()`. --- #### 2. EmailRenderer Type Check for Subscription **Location:** [EmailRenderer.php:121-137](file:///Users/dwindown/Local%20Sites/woonoow/app/public/wp-content/plugins/woonoow/includes/Core/Notifications/EmailRenderer.php#L121-L137) **Issue:** `get_recipient_email()` only checks for `WC_Order` and `WC_Customer`. For subscriptions, `$data` is an array, so recipient email extraction fails. **Impact:** Subscription emails may not find recipient email. **Recommendation:** Handle array data or subscription object in `get_recipient_email()`. --- #### 3. SubscriptionModule Sends to NotificationManager, Not EmailManager **Location:** [SubscriptionModule.php:529-531](file:///Users/dwindown/Local%20Sites/woonoow/app/public/wp-content/plugins/woonoow/includes/Modules/Subscription/SubscriptionModule.php#L529-L531) **Code:** ```php \WooNooW\Core\Notifications\NotificationManager::send($event_id, 'email', $data); ``` **Issue:** This goes through `NotificationManager`, which calls its own `send_email()` that uses `EmailRenderer::render()`. The `EmailRenderer::render()` method receives `$data['subscription']` but doesn't know how to handle it. **Impact:** Subscription email rendering may fail silently. --- #### 4. No Error Logging in Email Rendering Failures **Location:** [EmailRenderer.php:48-57](file:///Users/dwindown/Local%20Sites/woonoow/app/public/wp-content/plugins/woonoow/includes/Core/Notifications/EmailRenderer.php#L48-L57) **Issue:** When `get_template_settings()` returns null or `get_recipient_email()` returns null, the function returns null silently with only an empty debug log statement. **Recommendation:** Add proper `error_log()` calls for debugging. --- #### 5. Duplicate wp_mail Calls **Location:** Multiple places call `wp_mail()` directly: - `EmailManager::send_email()` (line 521) - `EmailManager::send_password_reset_email()` (line 406) - `NotificationManager::send_email()` (line 170) - `NotificationsController` test endpoint (line 1013) - `CampaignManager` (lines 275, 329) - `NewsletterController` (line 203) **Issue:** All these are intercepted by `WooEmailOverride`, which is correct. However, if `WooEmailOverride` is disabled (testing mode), all send synchronously. **Status:** Working as designed. --- ## Subscription Email Gap Analysis The subscription module has these events defined but needs variable population: | Event | Variables Needed | |-------|-----------------| | `subscription_pending_cancellation` | subscription_id, product_name, end_date | | `subscription_cancelled` | subscription_id, cancel_reason | | `subscription_expired` | subscription_id, product_name | | `subscription_paused` | subscription_id, product_name | | `subscription_resumed` | subscription_id, product_name | | `subscription_renewal_failed` | subscription_id, failed_count, payment_link | | `subscription_renewal_payment_due` | subscription_id, payment_link | | `subscription_renewal_reminder` | subscription_id, next_payment_date | **Required Fix:** Add subscription data handling to `EmailRenderer::get_variables()`. --- ## Recommendations ### High Priority 1. **Fix `EmailRenderer::get_variables()`** - Add handling for subscription data arrays 2. **Fix `EmailRenderer::get_recipient_email()`** - Handle array data with customer key ### Medium Priority 3. **Add error logging** - Replace empty debug conditions with actual logging 4. **Clean up debug conditions** - Many `if (defined('WP_DEBUG') && WP_DEBUG) {}` are empty ### Low Priority 5. **Consolidate email sending paths** - Consider routing all through one method 6. **Add email send failure tracking** - Log failed sends for troubleshooting --- ## Test Scripts Available | Script | Purpose | |--------|---------| | `check-settings.php` | Diagnose notification settings | | `test-email-flow.php` | Interactive email testing dashboard | | `test-email-direct.php` | Direct wp_mail testing | --- ## Documentation Comprehensive docs exist: - [NOTIFICATION_SYSTEM.md](file:///Users/dwindown/Local%20Sites/woonoow/app/public/wp-content/plugins/woonoow/NOTIFICATION_SYSTEM.md) - [EMAIL_DEBUGGING_GUIDE.md](file:///Users/dwindown/Local%20Sites/woonoow/app/public/wp-content/plugins/woonoow/EMAIL_DEBUGGING_GUIDE.md) --- ## Conclusion The email notification system is **production-ready** for order-related notifications. The main gap is **subscription email variable population**, which requires updates to `EmailRenderer.php` to properly handle subscription data and extract variables.