Files
WooNooW/.agent/reports/email-notification-audit-2026-01-29.md
Dwindi Ramadhana a0b5f8496d feat: Implement OAuth license activation flow
- Add LicenseConnect.tsx focused OAuth confirmation page in customer SPA
- Add /licenses/oauth/validate and /licenses/oauth/confirm API endpoints
- Update App.tsx to render license-connect outside BaseLayout (no header/footer)
- Add license_activation_method field to product settings in Admin SPA
- Create LICENSING_MODULE.md with comprehensive OAuth flow documentation
- Update API_ROUTES.md with license module endpoints
2026-01-31 22:22:22 +07:00

229 lines
9.3 KiB
Markdown

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