- 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
14 KiB
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_optionsor 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
- Admin goes to: Marketing > Newsletter > Campaigns
- Clicks: "Create Campaign"
- 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!
- Clicks: "Preview" → See full email with design + content merged
- Clicks: "Send Test Email" → Receive test at admin email
- Chooses: "Schedule for Later" → Select date/time
- Clicks: "Save & Schedule"
Sending Process
- WP-Cron runs every hour
- Finds campaigns where
status='scheduled'andscheduled_at <= now - Processes each campaign:
- Updates status to
sending - Gets all subscribers
- Sends in batches of 50
- Logs each send attempt
- Updates status to
sentwhen complete
- Updates status to
- 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
-
Separation of Concerns:
- Design templates = Visual layout (cards, buttons, colors)
- Campaign content = Message text (what to say)
-
Reuse Existing Infrastructure:
- Email builder (notification system)
- Email sending (notification system)
- Branding settings (email customization)
- Subscriber management (already built)
-
Minimal Duplication:
- Don't rebuild email builder
- Don't rebuild email sending
- Don't rebuild subscriber management
-
Efficient Workflow:
- Create design template once
- Reuse for multiple campaigns
- Only write campaign content each time
-
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
- Create database migration for campaign tables
- Build
CampaignsController.phpwith all API endpoints - Create
CampaignSender.phpwith batch processing logic - Add
newsletter_campaignevent to EventRegistry - Build Campaign UI components (list, editor, preview, stats)
- Test with small subscriber list
- Optimize batch size and delays
- Document for users