- 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
471 lines
14 KiB
Markdown
471 lines
14 KiB
Markdown
# 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
|