Files
WooNooW/NEWSLETTER_CAMPAIGN_PLAN.md
Dwindi Ramadhana 0b2c8a56d6 feat: Newsletter system improvements and validation framework
- Fix: Marketing events now display in Staff notifications tab
- Reorganize: Move Coupons to Marketing/Coupons for better organization
- Add: Comprehensive email/phone validation with extensible filter hooks
  - Email validation with regex pattern (xxxx@xxxx.xx)
  - Phone validation with WhatsApp verification support
  - Filter hooks for external API integration (QuickEmailVerification, etc.)
- Fix: Newsletter template routes now use centralized notification email builder
- Add: Validation.php class for reusable validation logic
- Add: VALIDATION_HOOKS.md documentation with integration examples
- Add: NEWSLETTER_CAMPAIGN_PLAN.md architecture for future campaign system
- Fix: API delete method call in Newsletter.tsx (delete -> del)
- Remove: Duplicate EmailTemplates.tsx (using notification system instead)
- Update: Newsletter controller to use centralized Validation class

Breaking changes:
- Coupons routes moved from /routes/Coupons to /routes/Marketing/Coupons
- Legacy /coupons routes maintained for backward compatibility
2025-12-26 10:59:48 +07:00

14 KiB

Newsletter Campaign System - Architecture Plan

Overview

A comprehensive newsletter system that separates design templates from campaign content, allowing efficient email broadcasting to subscribers without rebuilding existing infrastructure.


System Architecture

1. Subscriber Management (Already Built)

  • Location: Marketing > Newsletter > Subscribers List
  • Features:
    • Email collection with validation (format + optional external API)
    • Subscriber metadata (email, user_id, status, subscribed_at, ip_address)
    • Search/filter subscribers
    • Export to CSV
    • Delete subscribers
  • Storage: WordPress options table (woonoow_newsletter_subscribers)

2. Email Design Templates (Already Built - Reuse Notification System)

  • Location: Settings > Notifications > Email Builder
  • Purpose: Create the visual design/layout for newsletters
  • Features:
    • Visual block editor (drag-and-drop cards, buttons, text)
    • Markdown editor (mobile-friendly)
    • Live preview with branding (logo, colors, social links)
    • Shortcode support: {campaign_title}, {campaign_content}, {unsubscribe_url}, {subscriber_email}, {site_name}, etc.
  • Storage: Same as notification templates (wp_options or custom table)
  • Events to Create:
    • newsletter_campaign (customer, marketing category) - For broadcast emails

Template Structure Example:

[card:hero]
# {campaign_title}
[/card]

[card]
{campaign_content}
[/card]

[card:basic]
---
You're receiving this because you subscribed to our newsletter.
[Unsubscribe]({unsubscribe_url})
[/card]

3. Campaign Management 🆕 (New Module)

  • Location: Marketing > Newsletter > Campaigns (new tab)
  • Purpose: Create campaign content/message that uses design templates
  • Features:
    • Campaign list (draft, scheduled, sent, failed)
    • Create/edit campaign
    • Select design template
    • Write campaign content (rich text editor - text only, no design)
    • Preview (merge template + content)
    • Schedule or send immediately
    • Target audience (all subscribers, filtered by date, user_id, etc.)
    • Track status (pending, sending, sent, failed)

Database Schema

Table: wp_woonoow_campaigns

CREATE TABLE wp_woonoow_campaigns (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  title VARCHAR(255) NOT NULL,
  subject VARCHAR(255) NOT NULL,
  content LONGTEXT NOT NULL,
  template_id VARCHAR(100) DEFAULT 'newsletter_campaign',
  status ENUM('draft', 'scheduled', 'sending', 'sent', 'failed') DEFAULT 'draft',
  scheduled_at DATETIME NULL,
  sent_at DATETIME NULL,
  total_recipients INT DEFAULT 0,
  sent_count INT DEFAULT 0,
  failed_count INT DEFAULT 0,
  created_by BIGINT UNSIGNED,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX idx_status (status),
  INDEX idx_scheduled (scheduled_at),
  INDEX idx_created_by (created_by)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Table: wp_woonoow_campaign_logs

CREATE TABLE wp_woonoow_campaign_logs (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  campaign_id BIGINT UNSIGNED NOT NULL,
  subscriber_email VARCHAR(255) NOT NULL,
  status ENUM('pending', 'sent', 'failed') DEFAULT 'pending',
  error_message TEXT NULL,
  sent_at DATETIME NULL,
  INDEX idx_campaign (campaign_id),
  INDEX idx_status (status),
  FOREIGN KEY (campaign_id) REFERENCES wp_woonoow_campaigns(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

API Endpoints

Campaign CRUD

// GET /woonoow/v1/newsletter/campaigns
// List all campaigns with pagination
CampaignsController::list_campaigns()

// GET /woonoow/v1/newsletter/campaigns/{id}
// Get single campaign
CampaignsController::get_campaign($id)

// POST /woonoow/v1/newsletter/campaigns
// Create new campaign
CampaignsController::create_campaign($data)

// PUT /woonoow/v1/newsletter/campaigns/{id}
// Update campaign
CampaignsController::update_campaign($id, $data)

// DELETE /woonoow/v1/newsletter/campaigns/{id}
// Delete campaign
CampaignsController::delete_campaign($id)

// POST /woonoow/v1/newsletter/campaigns/{id}/preview
// Preview campaign (merge template + content)
CampaignsController::preview_campaign($id)

// POST /woonoow/v1/newsletter/campaigns/{id}/send
// Send campaign immediately or schedule
CampaignsController::send_campaign($id, $schedule_time)

// GET /woonoow/v1/newsletter/campaigns/{id}/stats
// Get campaign statistics
CampaignsController::get_campaign_stats($id)

// GET /woonoow/v1/newsletter/templates
// List available design templates
CampaignsController::list_templates()

UI Components

1. Campaign List Page

Route: /marketing/newsletter?tab=campaigns

Features:

  • Table with columns: Title, Subject, Status, Recipients, Sent Date, Actions
  • Filter by status (draft, scheduled, sent, failed)
  • Search by title/subject
  • Actions: Edit, Preview, Duplicate, Delete, Send Now
  • "Create Campaign" button

2. Campaign Editor

Route: /marketing/newsletter/campaigns/new or /marketing/newsletter/campaigns/{id}/edit

Form Fields:

- Campaign Title (internal name)
- Email Subject (what subscribers see)
- Design Template (dropdown: select from available templates)
- Campaign Content (rich text editor - TipTap or similar)
  - Bold, italic, links, headings, lists
  - NO design elements (cards, buttons) - those are in template
- Preview Button (opens modal with merged template + content)
- Target Audience (future: filters, for now: all subscribers)
- Schedule Options:
  - Send Now
  - Schedule for Later (date/time picker)
- Save as Draft

3. Preview Modal

Component: CampaignPreview.tsx

Features:

  • Fetch design template
  • Replace {campaign_title} with campaign title
  • Replace {campaign_content} with campaign content
  • Replace {unsubscribe_url} with sample URL
  • Show full email preview with branding
  • "Send Test Email" button (send to admin email)

4. Campaign Stats Page

Route: /marketing/newsletter/campaigns/{id}/stats

Metrics:

  • Total recipients
  • Sent count
  • Failed count
  • Sent date/time
  • Error log (for failed emails)

Sending System

WP-Cron Job

// Schedule hourly check for pending campaigns
add_action('woonoow_send_scheduled_campaigns', 'WooNooW\Core\CampaignSender::process_scheduled');

// Register cron schedule
if (!wp_next_scheduled('woonoow_send_scheduled_campaigns')) {
    wp_schedule_event(time(), 'hourly', 'woonoow_send_scheduled_campaigns');
}

Batch Processing

class CampaignSender {
    const BATCH_SIZE = 50; // Send 50 emails per batch
    const BATCH_DELAY = 5; // 5 seconds between batches
    
    public static function process_scheduled() {
        // Find campaigns where status='scheduled' and scheduled_at <= now
        $campaigns = self::get_pending_campaigns();
        
        foreach ($campaigns as $campaign) {
            self::send_campaign($campaign->id);
        }
    }
    
    public static function send_campaign($campaign_id) {
        $campaign = self::get_campaign($campaign_id);
        $subscribers = self::get_subscribers();
        
        // Update status to 'sending'
        self::update_campaign_status($campaign_id, 'sending');
        
        // Get design template
        $template = self::get_template($campaign->template_id);
        
        // Process in batches
        $batches = array_chunk($subscribers, self::BATCH_SIZE);
        
        foreach ($batches as $batch) {
            foreach ($batch as $subscriber) {
                self::send_to_subscriber($campaign, $template, $subscriber);
            }
            
            // Delay between batches to avoid rate limits
            sleep(self::BATCH_DELAY);
        }
        
        // Update status to 'sent'
        self::update_campaign_status($campaign_id, 'sent', [
            'sent_at' => current_time('mysql'),
            'sent_count' => count($subscribers),
        ]);
    }
    
    private static function send_to_subscriber($campaign, $template, $subscriber) {
        // Merge template with campaign content
        $email_body = self::merge_template($template, $campaign, $subscriber);
        
        // Send via notification system
        do_action('woonoow/notification/send', [
            'event' => 'newsletter_campaign',
            'channel' => 'email',
            'recipient' => $subscriber['email'],
            'subject' => $campaign->subject,
            'body' => $email_body,
            'data' => [
                'campaign_id' => $campaign->id,
                'subscriber_email' => $subscriber['email'],
            ],
        ]);
        
        // Log send attempt
        self::log_send($campaign->id, $subscriber['email'], 'sent');
    }
    
    private static function merge_template($template, $campaign, $subscriber) {
        $body = $template->body;
        
        // Replace campaign variables
        $body = str_replace('{campaign_title}', $campaign->title, $body);
        $body = str_replace('{campaign_content}', $campaign->content, $body);
        
        // Replace subscriber variables
        $body = str_replace('{subscriber_email}', $subscriber['email'], $body);
        $unsubscribe_url = add_query_arg([
            'action' => 'woonoow_unsubscribe',
            'email' => base64_encode($subscriber['email']),
            'token' => wp_create_nonce('unsubscribe_' . $subscriber['email']),
        ], home_url());
        $body = str_replace('{unsubscribe_url}', $unsubscribe_url, $body);
        
        // Replace site variables
        $body = str_replace('{site_name}', get_bloginfo('name'), $body);
        
        return $body;
    }
}

Workflow

Creating a Campaign

  1. Admin goes to: Marketing > Newsletter > Campaigns
  2. Clicks: "Create Campaign"
  3. Fills form:
    • Title: "Summer Sale 2025"
    • Subject: "🌞 50% Off Summer Collection!"
    • Template: Select "Newsletter Campaign" (design template)
    • Content: Write message in rich text editor
      Hi there!
      
      We're excited to announce our biggest summer sale yet!
      
      Get 50% off all summer items this week only.
      
      Shop now and save big!
      
  4. Clicks: "Preview" → See full email with design + content merged
  5. Clicks: "Send Test Email" → Receive test at admin email
  6. Chooses: "Schedule for Later" → Select date/time
  7. Clicks: "Save & Schedule"

Sending Process

  1. WP-Cron runs every hour
  2. Finds campaigns where status='scheduled' and scheduled_at <= now
  3. Processes each campaign:
    • Updates status to sending
    • Gets all subscribers
    • Sends in batches of 50
    • Logs each send attempt
    • Updates status to sent when complete
  4. Admin can view stats: total sent, failed, errors

Minimal Feature Set (MVP)

Phase 1: Core Campaign System

  • Database tables (campaigns, campaign_logs)
  • API endpoints (CRUD, preview, send)
  • Campaign list UI
  • Campaign editor UI
  • Preview modal
  • Send immediately functionality
  • Basic stats page

Phase 2: Scheduling & Automation

  • Schedule for later
  • WP-Cron integration
  • Batch processing
  • Error handling & logging

Phase 3: Enhancements (Future)

  • 📧 Open tracking (pixel)
  • 🔗 Click tracking (link wrapping)
  • 🎯 Audience segmentation (filter by date, user role, etc.)
  • 📊 Analytics dashboard
  • 📋 Campaign templates library
  • 🔄 A/B testing
  • 🤖 Automation workflows

Design Template Variables

Templates can use these variables (replaced during send):

Campaign Variables

  • {campaign_title} - Campaign title
  • {campaign_content} - Campaign content (rich text)

Subscriber Variables

  • {subscriber_email} - Subscriber's email
  • {unsubscribe_url} - Unsubscribe link

Site Variables

  • {site_name} - Site name
  • {site_url} - Site URL
  • {current_year} - Current year

File Structure

includes/
├── Api/
│   ├── NewsletterController.php (existing - subscribers)
│   └── CampaignsController.php (new - campaigns CRUD)
├── Core/
│   ├── Validation.php (existing - email/phone validation)
│   ├── CampaignSender.php (new - sending logic)
│   └── Notifications/
│       └── EventRegistry.php (add newsletter_campaign event)

admin-spa/src/routes/Marketing/
├── Newsletter.tsx (existing - subscribers list)
├── Newsletter/
│   ├── Campaigns.tsx (new - campaign list)
│   ├── CampaignEditor.tsx (new - create/edit)
│   ├── CampaignPreview.tsx (new - preview modal)
│   └── CampaignStats.tsx (new - stats page)

Key Principles

  1. Separation of Concerns:

    • Design templates = Visual layout (cards, buttons, colors)
    • Campaign content = Message text (what to say)
  2. Reuse Existing Infrastructure:

    • Email builder (notification system)
    • Email sending (notification system)
    • Branding settings (email customization)
    • Subscriber management (already built)
  3. Minimal Duplication:

    • Don't rebuild email builder
    • Don't rebuild email sending
    • Don't rebuild subscriber management
  4. Efficient Workflow:

    • Create design template once
    • Reuse for multiple campaigns
    • Only write campaign content each time
  5. Scalability:

    • Batch processing for large lists
    • Queue system for reliability
    • Error logging for debugging

Success Metrics

  • Admin can create campaign in < 2 minutes
  • Preview shows accurate email with branding
  • Emails sent without rate limit issues
  • Failed sends are logged and visible
  • No duplicate code or functionality
  • System handles 10,000+ subscribers efficiently

Next Steps

  1. Create database migration for campaign tables
  2. Build CampaignsController.php with all API endpoints
  3. Create CampaignSender.php with batch processing logic
  4. Add newsletter_campaign event to EventRegistry
  5. Build Campaign UI components (list, editor, preview, stats)
  6. Test with small subscriber list
  7. Optimize batch size and delays
  8. Document for users