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
This commit is contained in:
Dwindi Ramadhana
2025-12-26 10:59:48 +07:00
parent 0b08ddefa1
commit 0b2c8a56d6
23 changed files with 1132 additions and 232 deletions

470
NEWSLETTER_CAMPAIGN_PLAN.md Normal file
View File

@@ -0,0 +1,470 @@
# 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