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

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

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)
  • 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

  1. Add error logging - Replace empty debug conditions with actual logging
  2. Clean up debug conditions - Many if (defined('WP_DEBUG') && WP_DEBUG) {} are empty

Low Priority

  1. Consolidate email sending paths - Consider routing all through one method
  2. 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.