# 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**: ```markdown [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` ```sql 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` ```sql 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 ```php // 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**: ```tsx - 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 ```php // 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 ```php 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