Skip to content

Campaign System

Quick reference for AI agents working on campaign features.

Key Files

PurposeLocation
List Componentapps/frontend/src/components/campaigns/CampaignList.tsx
Detail Componentapps/frontend/src/components/campaigns/CampaignDetail.tsx
Planning Chatapps/frontend/src/components/campaigns/CampaignChat.tsx
Selector Dropdownapps/frontend/src/components/CampaignSelector.tsx
Backend Routesapps/backend/src/routes/campaigns.ts
Service Layerapps/backend/src/services/campaign.service.ts

Data Model

typescript
// Prisma model: CompanyCampaign
interface Campaign {
    id: string;
    companyId: string;
    userId: string;
    name: string;
    brief: string | null;
    goal: string | null;
    type: 'ONE_TIME' | 'RECURRING' | 'EVERGREEN';
    status: 'DRAFT' | 'ACTIVE' | 'PAUSED' | 'COMPLETED';
    startsAt: Date | null;
    endsAt: Date | null;
    // Lifecycle tracking
    pausedAt: Date | null;
    resumedAt: Date | null;
    completedAt: Date | null;
    lastStatusChangedAt: Date | null;
    isArchived: boolean;  // Separate from status!
    context: CampaignContext | null;  // JSON field
    planningHistory: object[] | null; // Chat history
}

IMPORTANT

isArchived is a SEPARATE BOOLEAN, not a status. Archived campaigns retain their actual status.

Context Pattern (Important!)

The context field uses Defaults + Overrides:

typescript
interface CampaignContext {
    keyMessages?: string[];      // Campaign-specific only
    targetAudience?: string;     // Overrides BrandProfile.targetAudience
    tone?: string;               // Overrides BrandProfile.voiceTone
    channels?: string[];         // Overrides connected account platforms
}

Rule: Absent fields inherit from Brand Profile. UI shows "Brand Default" badges.

API Endpoints

typescript
// List campaigns (excludes archived by default)
GET /api/campaigns?status=ACTIVE&type=ONE_TIME&search=q&includeArchived=false

// CRUD
POST /api/campaigns         // Create
GET /api/campaigns/:id      // Read
PUT /api/campaigns/:id      // Update
DELETE /api/campaigns/:id?action=delete_all  // Delete

// Content
GET /api/campaigns/:id/content  // All linked content
GET /api/campaigns/:id/analytics // Aggregated performance metrics

// Lifecycle
POST /api/campaigns/:id/resume   // Resume with strategy
POST /api/campaigns/:id/archive  // Archive/unarchive

// AI Features
POST /api/campaigns/chat              // Planning conversation
POST /api/campaigns/:id/generate-plan    // Extract plan from chat
POST /api/campaigns/:id/generate-content // Generate content drafts

// Bulk Operations
POST /api/campaigns/:id/duplicate     // Deep copy campaign
POST /api/campaigns/:id/find-replace  // Bulk text update

Common Patterns

Tenant Scoping

typescript
const tenantPrisma = getTenantPrisma(request.user.companyId);
const campaigns = await tenantPrisma.companyCampaign.findMany({...});

Archive Filtering

typescript
// Default behavior excludes archived
const where: any = { companyId };
if (!includeArchived) {
    where.isArchived = false;
}

Context Resolution (Frontend)

typescript
// Display effective value with fallback
const audience = campaign.context?.targetAudience 
    || brandProfile?.targetAudience 
    || 'General Audience';

// Show badge if using default
const isDefault = !campaign.context?.targetAudience;

Key Components

PurposeLocation
List Componentapps/frontend/src/components/campaigns/CampaignList.tsx
Detail Componentapps/frontend/src/components/campaigns/CampaignDetail.tsx
Resume Modalapps/frontend/src/components/campaigns/ResumeStrategyModal.tsx
Duplicate Modalapps/frontend/src/components/campaigns/DuplicateCampaignModal.tsx
Find & Replace Modalapps/frontend/src/components/campaigns/FindReplaceModal.tsx
Backend Routesapps/backend/src/routes/campaigns.ts
Ops Serviceapps/backend/src/services/campaign-ops.service.ts
Lifecycle Serviceapps/backend/src/services/campaign-lifecycle.service.ts
  • Brand Profile provides default audience/voice
  • Connected Accounts provide default channels
  • SchedulingConfig provides flood prevention settings
  • CompanyPost / CompanyBlogPost / CompanyVideoScript link via campaignId

TendSocial Documentation