Complete documentation for the email campaign management system with audience targeting, recipient tracking, and comprehensive analytics.
📚 Related Documentation:
- Email System - Email sending and template management
- SES Webhook Setup - Bounce and complaint handling
- System Architecture - Platform overview
- API Endpoints - Complete API reference
The Campaign System provides enterprise-grade email marketing capabilities with audience targeting, recipient tracking, and real-time analytics. Built on top of the Email System, it enables bulk email sending with personalization, scheduling, and comprehensive delivery tracking.
/admin/emails → Campaigns tab┌─────────────────────────────────────────────────────────────┐ │ FRONTEND (React) │ │ ├─ Campaign Service (src/services/email/campaignService.ts)│ │ ├─ Admin Dashboard (/admin/emails → Campaigns tab) │ │ └─ Campaign Components (CampaignsTab, CreateCampaignModal) │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ SUPABASE (Database + Edge Functions) │ │ ├─ campaigns table (Campaign metadata) │ │ ├─ campaign_recipients table (Individual tracking) │ │ ├─ email_actions table (Trigger-based emails) │ │ ├─ email_templates table (React Email templates) │ │ └─ email-api Edge Function (Email sending) │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ AMAZON SES (Email Delivery) │ │ ├─ Configuration Set (material-kai-emails) │ │ ├─ SNS Topic (ses-notifications) │ │ └─ Bounce/Complaint Tracking │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ WEBHOOK PROCESSING (Supabase Edge Function) │ │ └─ ses-webhook (Updates campaign_recipients status) │ └─────────────────────────────────────────────────────────────┘
Location: src/services/email/campaignService.ts
Complete campaign management service with methods:
Location: src/components/Admin/EmailManagement/
CampaignsTab.tsx - Main campaigns list
CreateCampaignModal.tsx - Campaign creation wizard
CampaignDetailsModal.tsx - Detailed campaign view
Location: Supabase PostgreSQL
campaigns - Campaign metadata
campaign_recipients - Individual recipient tracking
email_actions - Trigger-based emails
Location: supabase/functions/
email-api - Email sending with campaign support
ses-webhook - SES event processing
The campaigns table stores the following key fields: id (UUID primary key), name, description, template_id (references email_templates), status (one of: draft, scheduled, sending, sent, paused, cancelled), scheduled_at, sent_at, audience_filter (JSONB), recipient_count (auto-updated via trigger), subject_line, preview_text, from_name, from_email, reply_to, track_opens (boolean, default true), track_clicks (boolean, default true), tags (text array), metadata (JSONB), created_by, updated_by, created_at, and updated_at.
Indexes are created on status, scheduled_at, created_by, template_id, and a GIN index on tags.
RLS is enabled with an admin-only policy that checks membership in the admin role via user_profiles and roles tables.
Key Fields:
status - Campaign lifecycle stateaudience_filter - JSONB filter for recipient selectionrecipient_count - Auto-updated via triggertrack_opens/track_clicks - Enable/disable trackingtags - Array for campaign organizationmetadata - Additional custom dataThe campaign_recipients table stores: id (UUID primary key), campaign_id (references campaigns, cascade delete), email, user_id (references auth.users), contact_id (future use), variables (JSONB for personalization), status (one of: pending, sending, sent, failed, bounced, complained), tracking timestamps (sent_at, delivered_at, opened_at, clicked_at, bounced_at, complained_at), error_message, retry_count, email_log_id (references email_logs), created_at, and updated_at. A unique constraint enforces one email per campaign.
Indexes are created on campaign_id, email, status, and sent_at. RLS is enabled with the same admin-only policy.
Key Fields:
status - Recipient-specific delivery statusvariables - Personalization data for templateemail_log_id - Links to email_logs for detailed trackingTriggers:
campaigns.recipient_count on INSERT/DELETEupdated_at on UPDATEThe email_actions table maps system events to email templates for automatic sending. It stores: id, action_key (unique text identifier), action_name, description, template_id (references email_templates), is_active (default true), trigger_conditions (JSONB), created_at, and updated_at.
Default actions are pre-seeded for: welcome_email, password_reset, email_verification, quote_request, quote_response, and order_confirmation.
Purpose: Maps system events to email templates for automatic sending.
src/services/email/campaignService.ts
getCampaigns(filters?) — Get all campaigns with optional filtering by status, limit, and offset. Returns a Promise of Campaign array.
getCampaign(id) — Get campaign details with template information. Returns a Promise of Campaign.
createCampaign(data) — Create a new campaign with name, description, template_id, subject_line, preview_text, from_name, from_email, reply_to, audience_filter, and tags. Returns a Promise of Campaign.
updateCampaign(id, data) — Update campaign details (only for draft campaigns). Returns a Promise of Campaign.
deleteCampaign(id) — Delete a campaign (only draft campaigns). Returns a Promise of void.
scheduleCampaign(id, scheduledAt) — Schedule campaign for future delivery at the given Date.
sendCampaign(id) — Send campaign immediately. Creates recipients and queues emails.
pauseCampaign(id) — Pause a sending campaign.
cancelCampaign(id) — Cancel a scheduled campaign.
sendTestEmail(campaignId, email) — Send test email to specified address.
getCampaignRecipients(campaignId, filters?) — Get recipients for a campaign with optional filtering by status, limit, and offset.
getCampaignStats(campaignId) — Get comprehensive campaign statistics including totalRecipients, sent, delivered, opened, clicked, bounced, complained counts, and deliveryRate, openRate, clickRate, bounceRate, complaintRate percentages.
/admin/emails → Campaigns tab
Features:
Columns:
Steps:
Features:
Tabs:
Actions:
Statistics:
Call campaignService.createCampaign() with the campaign name, description, template_id, subject_line, preview_text, from_name, from_email, reply_to, audience_filter (e.g., { role: 'user', subscription_tier: 'pro' }), and tags.
Recipients are automatically added based on audience_filter when campaign is sent.
Audience Filter Examples:
{ type: 'all_users' }{ role: 'user' }{ subscription_tier: 'pro' }{ role: 'user', subscription_tier: ['pro', 'enterprise'], status: 'active' }Call campaignService.sendTestEmail(campaign.id, 'test@example.com') to send a test before launching.
Call campaignService.scheduleCampaign(campaign.id, scheduledDate) to schedule for a future time, or campaignService.sendCampaign(campaign.id) to send immediately.
Call campaignService.getCampaignStats(campaign.id) to retrieve delivery rate, open rate, bounce rate, and other metrics. Call campaignService.getCampaignRecipients(campaign.id, { status: 'bounced' }) to inspect recipients by status.
Create a welcome campaign targeting users created in the last 24 hours by setting audience_filter with a created_after timestamp. Then send immediately with campaignService.sendCampaign().
Create an announcement campaign targeting active pro and enterprise subscribers using audience_filter: { subscription_tier: ['pro', 'enterprise'], status: 'active' }. Schedule it for a future date/time using campaignService.scheduleCampaign().
Create a re-engagement campaign targeting users who haven't logged in for 30 days using a last_login_before filter. Send a test email first with campaignService.sendTestEmail(), then launch with campaignService.sendCampaign().
Define Clear Goals
Segment Your Audience
Test Before Sending
Optimize Send Times
Subject Lines
Preview Text
Email Body
Maintain List Hygiene
Monitor Metrics
Warm Up New Domains
Batch Sending
Track and Optimize
Symptoms: Campaign stuck in "sending" status
Solutions:
Query the campaign_recipients table for rows with status = 'failed' and the relevant campaign_id to identify failed recipients and their error messages.
Symptoms: Bounce rate > 5%
Solutions:
Query campaign_recipients for rows with status = 'bounced' and the relevant campaign_id to see email addresses, error messages, and bounce timestamps.
Symptoms: Low delivery rate
Solutions:
Query the email_logs table filtering by recipient email and ordering by created_at descending to see recent send attempts.
Symptoms: No open/click data
Solutions:
track_opens and track_clicks are enabledQuery the campaigns table for the relevant campaign_id to confirm track_opens and track_clicks are set to true.
Symptoms: campaign_recipients status not updating
Solutions:
supabase functions logs ses-webhook --tailQuery the campaigns and campaign_recipients tables with a join to compute per-campaign statistics including recipient_count, sent count, delivered count, opened count, clicked count, bounced count, complained count, delivery_rate, and bounce_rate. Filter to campaigns with status 'sent' and order by sent_at descending.
All campaign tables have RLS enabled with admin-only access. The policy checks that the authenticated user has the 'admin' role via the user_profiles and roles tables.
Trigger-based emails send automatically on system events (such as user signup triggering a welcome email) by linking an action key to a template in the email_actions table. No campaign is needed for these automated sends.
React Email templates are managed in /admin/emails → Templates tab. Each campaign references a template_id, and variables are substituted per recipient at send time.
SES webhook processing automatically updates campaign_recipients status when bounces, complaints, or deliveries are reported by Amazon SES via SNS. No manual intervention is needed. See docs/ses-webhook-setup.md for configuration details.
Campaign management via REST API is planned, with endpoints for listing campaigns, creating campaigns, sending campaigns, and retrieving campaign statistics.
Campaign event webhooks are planned and will deliver JSON payloads containing the event type (e.g., "campaign.sent"), campaign_id, timestamp, and statistics including total_recipients, sent, and delivered counts.
For questions or issues:
Last Updated: December 25, 2024 Version: 1.0.0 Status: Production Ready Maintainer: Development Team