# 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.
