Skip to main content

Campaign Management

This document covers the campaign management capabilities of the CRM & Marketing Platform.


Campaign Types

TypeDescriptionUse Case
ONE_TIMESingle sendTournament announcement
RECURRINGScheduled repeatsWeekly newsletter
TRIGGEREDEvent-basedBooking confirmation
JOURNEYMulti-step sequenceOnboarding series

Campaign Workflow

┌─────────────────────────────────────────────────────────────────┐
│ CAMPAIGN LIFECYCLE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ DRAFT ──▶ SCHEDULED ──▶ ACTIVE ──▶ COMPLETED │
│ │ │ │ │
│ │ │ ▼ │
│ │ │ PAUSED ─────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ Edit Edit/Cancel Resume/Cancel │
│ │
└─────────────────────────────────────────────────────────────────┘

Status Transitions

FromToTrigger
DRAFTSCHEDULEDUser schedules campaign
SCHEDULEDACTIVEScheduled time reached
SCHEDULEDDRAFTUser cancels schedule
ACTIVECOMPLETEDAll messages sent
ACTIVEPAUSEDUser pauses or error
PAUSEDACTIVEUser resumes
PAUSEDCANCELLEDUser cancels

Campaign Creation Flow

// 1. Create campaign
const campaign = await createCampaign({
name: "Summer Tournament 2024",
type: "ONE_TIME",
segmentId: "active-golfers-segment-id",
channels: ["EMAIL", "SMS", "FACEBOOK"],
scheduledAt: "2024-06-01T09:00:00Z",
timezone: "Africa/Johannesburg"
});

// 2. Configure channel content
await setCampaignContent(campaign.id, {
email: { templateId: "tournament-invite-email" },
sms: { templateId: "tournament-invite-sms" },
social: {
content: "Join us for the Summer Tournament 2024!",
mediaUrls: ["https://..."],
platforms: ["FACEBOOK", "INSTAGRAM"]
}
});

// 3. Schedule sends
await scheduleCampaign(campaign.id);
// → Creates jobs in BullMQ
// → Email/SMS → Messaging Service
// → Social → Social Publisher

Channels

ChannelDeliveryContent Source
EMAILMessaging ServiceTemplate ID
SMSMessaging ServiceTemplate ID
PUSHMessaging ServiceTemplate ID
WHATSAPPMessaging ServiceTemplate ID
SOCIALSocial PublisherInline content

Targeting

Segment-Based

// Target a pre-defined segment
const campaign = {
segmentId: "high-value-members",
// ...
};

Filter-Based

// Define inline filters (creates ad-hoc segment)
const campaign = {
filters: {
combinator: "AND",
rules: [
{ field: "membershipStatus", operator: "eq", value: "ACTIVE" },
{ field: "emailOptIn", operator: "eq", value: true }
]
},
// ...
};

Scheduling

Immediate Send

await executeCampaign(campaign.id);

Scheduled Send

await scheduleCampaign(campaign.id, {
scheduledAt: "2024-06-01T09:00:00Z",
timezone: "Africa/Johannesburg"
});

Recurring Schedule

const campaign = {
type: "RECURRING",
recurPattern: "0 9 * * 1", // Every Monday at 9am
recurEndAt: "2024-12-31T23:59:59Z",
timezone: "Africa/Johannesburg"
};

Execution

When a campaign is executed:

  1. Refresh segment - Get latest membership
  2. Sync to Messaging - Update contact list
  3. Filter by consent - Respect opt-outs per channel
  4. Send via channel - Messaging for email/SMS, Social for posts
  5. Track interactions - Record sent, delivered, etc.
  6. Update stats - Denormalized counts

Campaign Stats

MetricDescription
targetCountTotal in segment
sentCountSuccessfully sent
deliveredCountConfirmed delivered
openedCountEmail opens (unique)
clickedCountLink clicks (unique)
convertedCountConversions tracked
unsubscribedCountOpted out after
bouncedCountHard/soft bounces

Computed Rates

RateFormula
deliveryRatedelivered / sent
openRateopened / delivered
clickRateclicked / opened
conversionRateconverted / clicked

A/B Testing (Phase 4)

const campaign = {
type: "ONE_TIME",
abTest: {
enabled: true,
variants: [
{ name: "A", weight: 50, templateId: "template-a" },
{ name: "B", weight: 50, templateId: "template-b" }
],
winnerCriteria: "OPEN_RATE",
testDuration: "24h",
testPercentage: 20 // Send to 20%, then winner to rest
}
};