- 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
9.3 KiB
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
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 | Hooks WC order events, disables WC emails, routes to renderer |
| EmailRenderer.php | Renders templates, replaces variables, parses markdown |
| TemplateProvider.php | Manages templates, defaults, variable definitions |
| EventRegistry.php | Central registry of all notification events |
| NotificationManager.php | Validates settings, dispatches to channels |
| WooEmailOverride.php | Intercepts wp_mail via pre_wp_mail filter |
| 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
// 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 HTMLWooEmailOverride::interceptMail()catches viapre_wp_mailfilter- Returns
trueto short-circuit synchronous send
5. Queue & Async Send
MailQueue::enqueue()stores payload inwp_options(temp)- Schedules
woonoow/mail/sendaction via Action Scheduler MailQueue::sendNow()runs asynchronously:- Retrieves payload from options
- Disables
WooEmailOverrideto prevent loop - Calls actual
wp_mail() - Deletes temp option
Findings
✅ Working Correctly
- Async Email Queue: Properly prevents timeout issues
- Template System: Variables replaced correctly
- Event Registry: Single source of truth
- Subscription Events: Registered via
woonoow_notification_events_registryfilter - Global Toggle: WooNooW vs WooCommerce mode works
- WC Email Disable: Default emails properly disabled when WooNooW active
⚠️ Potential Issues
1. Missing Subscription Variable Population in EmailRenderer
Location: EmailRenderer.php:147-299
Issue: get_variables() handles WC_Order, WC_Product, WC_Customer but NOT subscription objects. Subscription notifications pass data like:
$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
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
Code:
\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
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)NotificationsControllertest 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
- Fix
EmailRenderer::get_variables()- Add handling for subscription data arrays - Fix
EmailRenderer::get_recipient_email()- Handle array data with customer key
Medium Priority
- Add error logging - Replace empty debug conditions with actual logging
- Clean up debug conditions - Many
if (defined('WP_DEBUG') && WP_DEBUG) {}are empty
Low Priority
- Consolidate email sending paths - Consider routing all through one method
- 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:
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.