Skip to main content

Fundraising Campaigns

Fundraising Campaigns

Component Files:

  • src/features/fundraising/components/MarketingCampaigns.tsx (Email)
  • src/features/fundraising/components/SMSCampaigns.tsx (Text)

Route: /fundraising (with tool='campaigns' or tool='sms') Access Level: Parent Org and Fund Users with Fundraising access (position-based) Last Updated: February 12, 2026 Status: ✅ Fully Integrated with Resend API (Email) + Text Templates

✨ Recent Updates (February 2026)

Marketing Edge Function Fixes & Cleanup (Feb 12, 2026)

  • P0 — Rewrote `process-scheduled-campaigns` edge function (v19):
  • Now delegates email sending to send-marketing-email instead of sending directly via Resend
  • Fixes: voided donors receiving emails (added status = 'active' filter), missing mailing-list recipient type, no MintBucks deduction, no rate limiting, incomplete variable replacement, one-by-one sending (now uses batch via delegation)
  • Added mailing_list_id support for mailing-list and custom-list recipient types
  • Extended donor data (last_name, household, giving stats) now passed for full variable replacement
  • P1 — Updated `send-marketing-email` edge function (v35):
  • Added explanatory @ts-nocheck comment (Deno imports)
  • Removed .throwOnError() on email_logs insert for successful sends (prevents logging failure from masking send success)
  • P2 — Frontend cleanup (`MarketingCampaigns.tsx`):
  • Removed 7 unused imports (useEvents, sanitizeEmailContent, fetchDonorPages, DonorPageRecord, fetchVideoBlasts, VideoBlast, fetchParentOrganization, EmailTemplateRecord, parseEmailContent, plainTextToBlocks)
  • Removed 7 unused lucide icons (Calendar, ChevronDown, X, Check, ChevronRight, Info, Copy)
  • Removed dead state: donorPages, VideoBlasts, organizationLogoUrl, upcomingPublishedEvents, formatDateWithWeekday
  • Removed dead useEffect that fetched donor pages, video blasts, and parent org logo
  • Replaced hardcoded hex colors in email preview with semantic theme tokens (bg-muted, bg-card, text-foreground, border-border, bg-sidebar-primary)
  • P2 — Fixed stale comment in `VideoBlastManager.tsx`: "fiscal sponsors" → "parent-org users"
  • Files Modified:
  • supabase/functions/process-scheduled-campaigns/index.ts — Full rewrite (v19)
  • supabase/functions/send-marketing-email/index.ts — Minor fixes (v35)
  • src/features/marketing/components/MarketingCampaigns.tsx — Dead code removal + styling fix
  • src/features/marketing/components/VideoBlastManager.tsx — Comment fix

✨ Recent Updates (January 2026)

Duplicate Campaign Feature (Jan 15, 2026)

  • New Feature: Duplicate sent campaigns to reuse them for future sends
  • Copy button appears in Actions column for sent campaigns
  • Creates a new draft with "(Copy)" suffix on the campaign name
  • Copies subject, content, recipient type, and reply-to settings
  • Opens the duplicated campaign in compose mode for editing
  • Requires write permission (canWrite)
  • FAQ Addressed: "Can I delete old Sent Campaigns or Reuse them in the future?"
  • Delete: Sent campaigns cannot be deleted (by design for audit/compliance)
  • Reuse: Use the new Duplicate button to copy a sent campaign as a new draft
  • Files Modified:
  • src/lib/db.ts - Added duplicateCampaign() function
  • src/hooks/useSupabaseData.ts - Added useDuplicateCampaign() hook
  • src/components/campaigns/CampaignsDashboard.tsx - Added duplicate button and handler

Heading Size Preview Fix (Jan 19, 2026)

  • Bug Fixed: Medium/Large heading size buttons now correctly update the input field size in real-time
  • Root cause: Input component's base styles (h-9, text-base, md:text-sm) were overriding the dynamic size classes
  • Solution: Added explicit inline styles with !h-auto override and getFontSize() function
  • Large = 1.5rem, Medium = 1.25rem, Small = 1.125rem
  • Files Modified:
  • src/features/marketing/components/EmailBuilder/blocks/HeadingBlock.tsx - Added inline style overrides

Email Builder UX Improvements (Jan 5, 2026)

  • Heading Labels Updated: Changed technical H1/H2/H3 labels to user-friendly Large/Medium/Small
  • Added size selector buttons in heading block editing panel
  • Small (H3) = text-lg, Medium (H2) = text-xl, Large (H1) = text-2xl
  • Control Positioning Improved:
  • X delete button moved with 8px top padding (from tight corner)
  • Up/down arrows and X button now have z-20 to stay on top of images
  • Controls remain visible even when user uploads large images
  • Files Modified:
  • src/features/marketing/components/EmailBuilder/utils/blockTypes.ts - Updated description
  • src/features/marketing/components/EmailBuilder/blocks/HeadingBlock.tsx - Added size selector
  • src/features/marketing/components/EmailBuilder/BuilderCanvas.tsx - Fixed X button position and z-index

Clickable Email Variables Panel (Jan 1, 2026)

  • New Feature: Variables tab added to EmailBuilder sidebar
  • Three-tab sidebar: Blocks | Variables | Design
  • Click any variable to copy to clipboard with toast confirmation
  • Variables organized by category with descriptions
  • Tooltips explain what each variable does
  • Supported Variables (processed at send time):
  • {{first_name}} - Recipient's first name
  • {{last_name}} - Recipient's last name
  • {{name}} - Recipient's full name
  • {{email}} - Recipient's email address
  • {{organization_name}} - Sending organization name
  • Coming Soon Variables (displayed but not yet processed):
  • Donor variables: {{donor_since}}, {{last_gift_amount}}, {{total_giving}}, etc.
  • Household variables: {{household_name}}, {{primary_contact}}
  • Event variables: {{event_name}}, {{event_date}}, {{event_location}}
  • Files Added:
  • src/features/marketing/components/EmailBuilder/VariablesPanel.tsx
  • Files Modified:
  • src/features/marketing/components/EmailBuilder/EmailBuilder.tsx - Added Variables tab
  • backend/supabase/functions/send-marketing-email/index.ts - Enhanced variable processing

Bug Fix: Individual Recipient Email (Jan 1, 2026)

  • Fixed: Individual recipient selector was storing name instead of email
  • Dropdown now correctly stores email address as the value
  • Recipients without email addresses are filtered out
  • Fixes campaign send failure when using "Individual Email" recipient type
  • Files Modified:
  • src/features/marketing/components/MarketingCampaigns.tsx

✨ Recent Updates (December 2025)

Visual Email Builder (Dec 30, 2025)

  • New Feature: Visual email builder for creating professional emails
  • Toggle between "Classic Editor" (textarea) and "Visual Builder ✨" modes
  • Block-based editing with live preview showing exactly how email will appear
  • Blocks available: Text, Heading (Large/Medium/Small), Image, Button, Divider, Spacer
  • Click blocks to add from palette, reorder using up/down arrows
  • Click block to select and edit properties inline
  • Delete blocks using X button (top-right of each block)
  • Keyboard shortcuts: Delete (remove), Ctrl+D (duplicate), Ctrl+Z (undo), Esc (deselect)
  • Undo/Redo support with 20 levels of history
  • Technical Implementation:
  • Uses @dnd-kit/core and @dnd-kit/sortable for drag-and-drop
  • Blocks stored as JSON in content column (backward compatible with plain text)
  • HTML rendered at send time via renderBlocksToHtml() function
  • Email-safe HTML with table-based layouts for maximum client compatibility
  • Files Added:
  • src/features/marketing/components/EmailBuilder/ - Main builder component
  • EmailBuilder.tsx - Container with toolbar and keyboard shortcuts
  • BlockPalette.tsx - Draggable block list sidebar
  • BuilderCanvas.tsx - Drop zone with live preview
  • BlockRenderer.tsx - Renders blocks to React components
  • blocks/ - Individual block components (TextBlock, HeadingBlock, etc.)
  • utils/blockTypes.ts - TypeScript type definitions
  • utils/blockDefaults.ts - Default props for new blocks
  • utils/renderToHtml.ts - Converts blocks to email HTML
  • hooks/useEmailBuilder.ts - State management hook
  • Files Modified:
  • src/components/campaigns/CampaignCompose.tsx - Added toggle and visual builder
  • src/features/marketing/components/MarketingCampaigns.tsx - Added toggle and visual builder
  • Migration: Classic editor preserved, users can toggle between modes

Add Links Feature Unified (Dec 22, 2025)

  • Email Campaigns: Added unified "Add Links (Event / VideoBlast / Donor Page)" section
  • Matches SMS campaigns structure for consistency
  • Checkbox reveals three dropdowns: Event, VideoBlast, Donor Page
  • Copy button inserts selected link into email body
  • Tooltip explains link insertion functionality
  • Text Campaigns: Fixed event dropdown not appearing for parent orgs
  • useEvents hook now accepts entityOverride parameter
  • SMS passes effectiveEntityId to fetch events for selected organization
  • Technical Fix: useEvents hook updated to support entity override for components that need to fetch data for a specific organization (e.g., when parent org selects a nonprofit from dropdown)

Terminology Update (Dec 22, 2025)

  • SMS → Text: All user-facing references to "SMS" changed to "Text" for better user understanding
  • "SMS Campaigns" → "Text Campaigns"
  • "SMS Templates" → "Text Templates"
  • "Compose SMS" → "Compose Text Message"
  • Internal component/file names unchanged to avoid import refactoring

UX Improvements (Dec 19, 2025)

  • Text Character Limit: Enforced 160 character limit for single text messages
  • Character counter shows remaining characters with visual warning when over limit
  • Tooltip on message field explains compliance requirements
  • Strategic Tooltips Added:
  • Text: Message field (compliance tips), Add Links checkbox (explains link insertion)
  • Email: Reply-To field (explains where replies go), From Organization (branding info)
  • Donor Pages: Recurring donation checkbox (explains benefits and cancellation)

Text Campaigns Enhancements (Dec 18, 2025)

  • Event Selector: Checkbox to add event details to text messages
  • Dropdown shows upcoming published events
  • Copy button inserts event name, date, location, and registration URL
  • Link Section: Checkbox to add VideoBlast or Donor Page links
  • VideoBlast dropdown with copy button to insert video URL
  • Donor Page dropdown with copy button to insert donation URL
  • Tooltip explains that links count toward 160 character limit
  • Text Templates Table: New sms_templates table in Supabase
  • System templates for Thank You, Event Reminders, Event Invitation
  • Custom templates per organization with RLS policies
  • Variable Support: {{first_name}}, {{event_name}}, {{donate_link}}, etc.

Email Composer Enhancements (Dec 17, 2025)

  • Branded Email Template: All outgoing emails now use a professional branded wrapper
  • Header displays fund/organization name with gradient background
  • Clean white body section with proper typography
  • Footer with "Sent with ❤️ by [Fund Name]" and Alignmint branding
  • Preview shows exact email appearance before sending
  • Template Tooltips & Variable Reference:
  • HelpCircle tooltip on "Use a Template" explaining template functionality
  • Collapsible "Available Variables" section listing all supported tokens
  • Recipient Variables: {{donor_name}}, {{first_name}}, {{last_name}}, {{email}}
  • Organization Variables: {{organization_name}}
  • Event Variables: {{event_name}}, {{event_date}}, {{event_time}}, {{event_location}}, {{event_link}}
  • Media & Links: {{media}}, {{donation_portal}}, {{unsubscribe_link}}
  • Donor Page Integration: Dynamic dropdown fetches donor pages from donor_pages table
  • Auto-selects organization's default system page when "Include Donate Now" is checked
  • Donate button uses proper URL format: https://donate.alignmint.app/{org-id-prefix}/{org-slug}/{page-slug}
  • Event Integration: "Add Event Details" checkbox reveals event selector
  • Dynamically fetches published upcoming events from Events & Ticketing
  • "Copy Event Details" button copies event name, date, time, location, and registration URL to clipboard
  • Only visible when published upcoming events exist
  • Media Section Redesign: Hidden behind "Insert Photo/Video" checkbox
  • Two-column layout: Upload area (left) + VideoBlast Library (right)
  • VideoBlast library shows scrollable tiles with thumbnail, title, date
  • Visual checkmark on selected video
  • Upload and VideoBlast selection are mutually exclusive
  • Template Dropdown: Removed "View All Templates" option, shows all templates directly

Email Sending Integration

  • Edge Function deployed: send-marketing-email on Supabase
  • Email provider: Resend API (RESEND_API_KEY secret configured)
  • Personalization: Supports the following tokens (replaced at send time):
  • {{first_name}} - First name (extracted from full name)
  • {{last_name}} - Last name (extracted from full name)
  • {{name}} - Full name
  • {{email}} - Recipient email address
  • {{organization_name}} - Sending organization name
  • Unsubscribe link: Automatically appended to all marketing emails
  • Email logging: All sends logged to email_logs table

Data Flow

1. User composes email in MarketingCampaigns component 2. Campaign saved to marketing_campaigns table via createCampaign() 3. sendMarketingEmails() invokes send-marketing-email Edge Function 4. Edge Function sends via Resend API, logs to email_logs 5. Campaign status updated to 'sent' with recipient count

Edge Function Details

  • Location: backend/supabase/functions/send-marketing-email/index.ts
  • Endpoint: https://zlokhayitthdzitjysht.supabase.co/functions/v1/send-marketing-email
  • Required secrets: RESEND_API_KEY, SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY

Recipient Types Supported

  • All Donors (from donors table)
  • All Volunteers (from volunteers table)
  • All Prospects (from prospects table)
  • Individual email address

---

Overview

The Marketing Campaigns manager enables organizations to create, send, and track email marketing campaigns. It includes email template management, recipient list building, campaign scheduling, and analytics tracking (open rates, click rates). Users can segment audiences, personalize messages, and measure campaign effectiveness.

UI Features

Main Features

  • Campaign List View:
  • Table of all campaigns
  • Filter by status (Draft, Scheduled, Sent)
  • Search by name or subject
  • Quick actions menu
  • Create Campaign:
  • Campaign name
  • Email subject line
  • Recipient selection (segments)
  • Email template selection
  • Email composer (rich text)
  • Preview mode
  • Schedule or send immediately
  • Campaign Analytics:
  • Open rate
  • Click rate
  • Bounce rate
  • Unsubscribe rate
  • Recipient list
  • Link click tracking
  • Email Templates:
  • Pre-built templates
  • Custom templates
  • Template editor
  • Variable placeholders

Campaign Table Columns

  • Campaign Name
  • Subject Line
  • Recipients (count)
  • Status Badge (Draft, Scheduled, Sent)
  • Sent/Scheduled Date
  • Open Rate (%)
  • Click Rate (%)
  • Actions dropdown

Create Campaign Form

  • Campaign Details:
  • Name (internal)
  • Subject line
  • Preview text
  • From name
  • Reply-to email
  • Recipients:
  • All Donors
  • All Volunteers
  • Active Donors (donated in last 12 months)
  • Major Donors (>$1000 lifetime)
  • Custom segment
  • Individual selection
  • Email Content:
  • Template selection
  • Rich text editor
  • Image upload
  • Personalization tokens ({{first_name}}, {{last_donation}}, etc.)
  • Preview button
  • Scheduling:
  • Send now
  • Schedule for later (date/time picker)
  • Time zone selection

Campaign Analytics View

  • Summary Cards:
  • Total Sent
  • Opened (count and %)
  • Clicked (count and %)
  • Bounced (count and %)
  • Recipient Table:
  • Name
  • Email
  • Status (Sent, Opened, Clicked, Bounced)
  • Open time
  • Click count
  • Link Performance:
  • URL
  • Click count
  • Unique clicks

Data Requirements

Campaign Data

  • id (uuid) - Unique identifier
  • organization_id (uuid) - Organization owner
  • name (string) - Campaign name (internal)
  • subject (string) - Email subject line
  • preview_text (string, nullable) - Preview text
  • from_name (string) - Sender name
  • reply_to (string) - Reply-to email
  • recipients_segment (string) - Recipient segment type
  • recipient_count (integer) - Number of recipients
  • email_template_id (uuid, nullable) - Template used
  • email_content (text) - Email HTML content
  • status (string) - 'draft', 'scheduled', 'sending', 'sent'
  • scheduled_at (datetime, nullable) - When scheduled to send
  • sent_at (datetime, nullable) - When sent
  • total_sent (integer) - Total emails sent
  • total_opened (integer) - Total opens
  • total_clicked (integer) - Total clicks
  • total_bounced (integer) - Total bounces
  • total_unsubscribed (integer) - Total unsubscribes
  • open_rate (decimal) - Open rate percentage
  • click_rate (decimal) - Click rate percentage
  • created_by_id (uuid) - User who created
  • created_at (datetime) - When created
  • updated_at (datetime) - When updated

Campaign Recipient Data

  • id (uuid) - Unique identifier
  • campaign_id (uuid) - Campaign reference
  • recipient_type (string) - 'donor', 'volunteer', 'contact'
  • recipient_id (uuid) - Donor/volunteer/contact ID
  • email (string) - Email address
  • status (string) - 'sent', 'opened', 'clicked', 'bounced', 'unsubscribed'
  • sent_at (datetime) - When sent
  • opened_at (datetime, nullable) - First open time
  • open_count (integer) - Number of opens
  • click_count (integer) - Number of clicks
  • bounced_at (datetime, nullable) - When bounced
  • bounce_reason (string, nullable) - Bounce reason

Email Template Data

  • id (uuid) - Unique identifier
  • organization_id (uuid) - Organization owner
  • name (string) - Template name
  • description (text, nullable) - Template description
  • html_content (text) - HTML template
  • thumbnail_url (string, nullable) - Preview image
  • is_system (boolean) - System template vs custom
  • created_at (datetime) - When created
  • updated_at (datetime) - When updated

Data Mutations

  • Create Campaign: Create new campaign
  • Update Campaign: Edit draft campaign
  • Delete Campaign: Delete draft campaign
  • Send Campaign: Send campaign immediately
  • Schedule Campaign: Schedule campaign for later
  • Cancel Scheduled: Cancel scheduled campaign
  • Create Template: Create email template
  • Update Template: Edit email template
  • Delete Template: Delete custom template

API Endpoints Required

GET /api/v1/campaigns

Description: Fetch campaigns
Query Parameters:
  - organization_id (required, uuid)
  - status (optional, string) - 'draft', 'scheduled', 'sent', 'all'
  - search (optional, string) - Search name or subject
  - page (optional, integer, default: 1)
  - per_page (optional, integer, default: 25)
  - sort_by (optional, string, default: 'created_desc')

Response: {
  data: [
    {
      id: "uuid",
      organization_id: "uuid",
      name: "Year-End Appeal 2024",
      subject: "Make a Difference This Year",
      recipients_segment: "all_donors",
      recipient_count: 245,
      status: "sent",
      sent_at: "2024-11-01T10:00:00Z",
      total_sent: 245,
      total_opened: 167,
      total_clicked: 59,
      open_rate: 68.16,
      click_rate: 24.08,
      created_by: "Jane Smith",
      created_at: "2024-10-25T14:30:00Z"
    }
  ],
  meta: {
    total: 24,
    page: 1,
    per_page: 25,
    total_pages: 1
  }
}

GET /api/v1/campaigns/:id

Description: Get campaign details
Path Parameters:
  - id (required, uuid)
Query Parameters:
  - organization_id (required, uuid)

Response: {
  data: {
    id: "uuid",
    ...all campaign fields,
    email_content: "<html>...</html>",
    recipients: [
      {
        id: "uuid",
        email: "john@example.com",
        status: "opened",
        opened_at: "2024-11-01T11:30:00Z",
        open_count: 2,
        click_count: 1
      }
    ],
    link_performance: [
      {
        url: "https://donate.example.com",
        click_count: 45,
        unique_clicks: 38
      }
    ]
  }
}

POST /api/v1/campaigns

Description: Create new campaign
Request Body: {
  organization_id: "uuid",
  name: "Year-End Appeal 2024",
  subject: "Make a Difference This Year",
  preview_text: "Your support changes lives",
  from_name: "Awakenings Team",
  reply_to: "info@awakenings.org",
  recipients_segment: "all_donors",
  email_template_id: "uuid",
  email_content: "<html>...</html>",
  status: "draft"
}

Response: {
  data: {
    id: "uuid",
    ...all campaign fields,
    recipient_count: 245
  },
  message: "Campaign created successfully"
}

PUT /api/v1/campaigns/:id

Description: Update campaign (draft only)
Path Parameters:
  - id (required, uuid)
Request Body: {
  organization_id: "uuid",
  name: "Updated Name",
  subject: "Updated Subject",
  ...other fields
}

Response: {
  data: {
    id: "uuid",
    ...updated fields
  },
  message: "Campaign updated successfully"
}

Note: Can only update draft campaigns

DELETE /api/v1/campaigns/:id

Description: Delete campaign (draft only)
Path Parameters:
  - id (required, uuid)
Query Parameters:
  - organization_id (required, uuid)

Response: {
  message: "Campaign deleted successfully"
}

POST /api/v1/campaigns/:id/send

Description: Send campaign immediately
Path Parameters:
  - id (required, uuid)
Request Body: {
  organization_id: "uuid"
}

Response: {
  data: {
    id: "uuid",
    status: "sending",
    sent_at: "2024-11-08T15:00:00Z",
    total_sent: 245
  },
  message: "Campaign is being sent"
}

Note: Async processing, status updates to 'sent' when complete

POST /api/v1/campaigns/:id/schedule

Description: Schedule campaign for later
Path Parameters:
  - id (required, uuid)
Request Body: {
  organization_id: "uuid",
  scheduled_at: "2024-11-15T10:00:00Z",
  timezone: "America/Los_Angeles"
}

Response: {
  data: {
    id: "uuid",
    status: "scheduled",
    scheduled_at: "2024-11-15T10:00:00Z"
  },
  message: "Campaign scheduled successfully"
}

POST /api/v1/campaigns/:id/cancel_schedule

Description: Cancel scheduled campaign
Path Parameters:
  - id (required, uuid)
Request Body: {
  organization_id: "uuid"
}

Response: {
  data: {
    id: "uuid",
    status: "draft",
    scheduled_at: null
  },
  message: "Campaign schedule cancelled"
}

POST /api/v1/campaigns/:id/test_send

Description: Send test email
Path Parameters:
  - id (required, uuid)
Request Body: {
  organization_id: "uuid",
  test_emails: ["test@example.com", "admin@example.com"]
}

Response: {
  message: "Test email sent to 2 recipients"
}

GET /api/v1/campaigns/recipient_count

Description: Get recipient count for segment
Query Parameters:
  - organization_id (required, uuid)
  - segment (required, string) - Segment type

Response: {
  data: {
    segment: "all_donors",
    count: 245,
    preview: [
      { name: "John Doe", email: "john@example.com" },
      { name: "Jane Smith", email: "jane@example.com" }
    ]
  }
}

GET /api/v1/email_templates

Description: Get email templates
Query Parameters:
  - organization_id (required, uuid)
  - include_system (optional, boolean, default: true)

Response: {
  data: [
    {
      id: "uuid",
      name: "Donation Appeal",
      description: "Standard donation appeal template",
      thumbnail_url: "https://...",
      is_system: true
    },
    {
      id: "uuid",
      name: "Custom Newsletter",
      description: "Monthly newsletter",
      thumbnail_url: "https://...",
      is_system: false
    }
  ]
}

POST /api/v1/email_templates

Description: Create email template
Request Body: {
  organization_id: "uuid",
  name: "Custom Template",
  description: "My custom template",
  html_content: "<html>...</html>"
}

Response: {
  data: {
    id: "uuid",
    ...template fields
  },
  message: "Template created successfully"
}

Request/Response Schemas

Campaign Schema

interface Campaign {
  id: string;
  organization_id: string;
  name: string;
  subject: string;
  preview_text?: string;
  from_name: string;
  reply_to: string;
  recipients_segment: string;
  recipient_count: number;
  email_template_id?: string;
  email_content: string;
  status: 'draft' | 'scheduled' | 'sending' | 'sent';
  scheduled_at?: string;
  sent_at?: string;
  total_sent: number;
  total_opened: number;
  total_clicked: number;
  total_bounced: number;
  total_unsubscribed: number;
  open_rate: number;
  click_rate: number;
  created_by: string;
  created_at: string;
  updated_at: string;
}

Authentication & Authorization

Required Permissions

  • campaigns:read - View campaigns
  • campaigns:write - Create and edit campaigns
  • campaigns:delete - Delete campaigns
  • campaigns:send - Send campaigns

Role-Based Access

  • Admin: Full access to all operations
  • Manager: Can create, edit, send campaigns
  • Staff: Can view campaigns only
  • Volunteer: No access

Business Logic & Validations

Frontend Validations

  • Campaign name required
  • Subject line required (max 150 chars)
  • From name required
  • Reply-to email required and valid format
  • At least one recipient required
  • Email content required
  • Scheduled date must be in future

Backend Validations (Rails)

  • Valid email addresses
  • Recipient segment must exist
  • Cannot edit/delete sent campaigns
  • Cannot send campaign with no recipients
  • Scheduled time must be at least 15 minutes in future
  • Email content must be valid HTML
  • Rate limiting on sends

Business Rules

  • Draft campaigns can be edited
  • Sent campaigns are immutable
  • Scheduled campaigns can be cancelled
  • Email tracking via pixel and link wrapping
  • Unsubscribe link required in all emails
  • Bounce handling and list cleaning
  • Personalization tokens replaced at send time
  • Analytics updated in real-time
  • Failed sends retried automatically

State Management

Local State

  • campaigns - List of campaigns
  • selectedCampaign - Currently viewing/editing
  • createCampaignOpen - Create dialog state
  • emailContent - Email editor content
  • selectedTemplate - Selected email template
  • recipientSegment - Selected recipient segment
  • recipientCount - Calculated recipient count

Global State (AppContext)

  • selectedEntity - Current organization

Dependencies

Internal Dependencies

  • AppContext - Global state
  • Mock data - TO BE REMOVED - mockCampaigns, emailTemplates
  • UI components (Card, Button, Table, Dialog, etc.)
  • Rich text editor component

External Libraries

  • lucide-react - Icons
  • sonner - Toast notifications
  • Rich text editor (TipTap, Quill, etc.)
  • Email service provider SDK (SendGrid, Mailgun, etc.)

Error Handling

Error Scenarios

1. Network Error: Show toast "Unable to load campaigns", retry 2. Validation Error: Show inline field errors 3. Send Failed: Show error "Failed to send campaign" 4. No Recipients: Show error "No recipients match this segment" 5. Cannot Edit: Show error "Cannot edit sent campaigns" 6. Permission Error: Show toast "You don't have permission"

Loading States

  • Initial load: Skeleton table
  • Campaign creation: Loading spinner
  • Sending: Progress indicator with "Sending to 245 recipients..."
  • Analytics: Loading spinner while fetching stats

Mock Data to Remove

  • MarketingCampaigns.tsx - mockCampaigns array
  • emailTemplates.ts - Mock email templates
  • Move interfaces to src/types/campaign.ts

Migration Notes

Phase 1: API Integration

1. Create src/api/campaigns.ts 2. Create src/types/campaign.ts 3. Implement campaign list 4. Implement create/edit campaign

Phase 2: Email Service

1. Choose email service provider (SendGrid, Mailgun, etc.) 2. Integrate email sending API 3. Implement email tracking 4. Test deliverability

Phase 3: Templates

1. Implement template management 2. Create default templates 3. Implement template editor 4. Test personalization tokens

Phase 4: Analytics

1. Implement open tracking 2. Implement click tracking 3. Implement bounce handling 4. Create analytics dashboard

Related Documentation


Synced from IFMmvp-Frontend documentation: pages/marketing/01-MARKETING-CAMPAIGNS.md

Ready to Get Started?

See how Alignmint can simplify your nonprofit's operations. Schedule a free demo with our team and we'll walk you through everything.

Questions? Email us at steven@getalignmint.org

Ready to get started?Start Plus Trial