Headlessly
Marketing

Campaign

Marketing initiatives -- email drips, social pushes, content launches, paid ads, webinars, and referral programs.

Schema

import { Noun } from 'digital-objects'

export const Campaign = Noun('Campaign', {
  name: 'string!',
  slug: 'string##',
  description: 'string',
  type: 'Email | Social | Content | Event | Paid | Webinar | Referral',
  status: 'Draft | Scheduled | Active | Launched | Paused | Completed | Cancelled',
  startDate: 'date',
  endDate: 'date',
  launchedAt: 'datetime',
  budget: 'number',
  actualCost: 'number',
  currency: 'string',
  targetLeads: 'number',
  targetRevenue: 'number',
  leads: '<- Lead.campaign[]',
  actualLeads: 'number',
  actualRevenue: 'number',
  roi: 'number',
  landingPageUrl: 'string',
  utmSource: 'string',
  utmMedium: 'string',
  utmCampaign: 'string',
  organization: '-> Organization',
  owner: '-> Contact',
  launch: 'Launched',
  pause: 'Paused',
  complete: 'Completed',
})

Fields

FieldTypeRequiredDescription
namestringYesCampaign name
slugstring (unique, indexed)NoURL-safe identifier for the campaign
descriptionstringNoCampaign description and goals
typeenumNoChannel: Email, Social, Content, Event, Paid, Webinar, or Referral
statusenumNoLifecycle stage: Draft, Scheduled, Active, Launched, Paused, Completed, or Cancelled
startDatedateNoPlanned start date
endDatedateNoPlanned end date
launchedAtdatetimeNoActual launch timestamp
budgetnumberNoAllocated budget
actualCostnumberNoActual spend to date
currencystringNoCurrency code (e.g. USD, EUR)
targetLeadsnumberNoTarget number of leads to generate
targetRevenuenumberNoTarget revenue from this campaign
leads<- Lead[]NoLeads generated by this campaign
actualLeadsnumberNoActual leads generated
actualRevenuenumberNoActual revenue attributed
roinumberNoReturn on investment (computed)
landingPageUrlstringNoCampaign landing page URL
utmSourcestringNoUTM source parameter
utmMediumstringNoUTM medium parameter
utmCampaignstringNoUTM campaign parameter
organization-> OrganizationNoTenant this campaign belongs to
owner-> ContactNoCampaign owner or manager

Relationships

FieldDirectionTargetDescription
leads<-Lead.campaign[]Leads attributed to this campaign
organization->OrganizationTenant this campaign belongs to
owner->ContactPerson responsible for this campaign

Verbs

VerbEventDescription
createCreatedCreate a new campaign in Draft status
updateUpdatedUpdate campaign fields
deleteDeletedRemove a campaign
launchLaunchedStart the campaign -- sets status to Launched, records launchedAt
pausePausedTemporarily halt the campaign
completeCompletedMark the campaign as finished

Verb Lifecycle

import { Campaign } from '@headlessly/marketing'

// BEFORE hook -- validate before launch
Campaign.launching(campaign => {
  if (!campaign.landingPageUrl) {
    throw new Error('Landing page URL required before launch')
  }
  if (!campaign.budget || campaign.budget <= 0) {
    throw new Error('Budget must be set before launch')
  }
})

// Execute
await Campaign.launch('campaign_e5JhLzXc')

// AFTER hook -- react to launch
Campaign.launched((campaign, $) => {
  $.Event.create({
    name: 'campaign_launched',
    type: 'track',
    source: 'API',
    properties: JSON.stringify({
      campaignId: campaign.$id,
      type: campaign.type,
      budget: campaign.budget,
    }),
    timestamp: new Date().toISOString(),
  })

  // Create a funnel to track this campaign
  $.Funnel.create({
    name: `${campaign.name} Conversion`,
    steps: JSON.stringify([
      { name: 'Landing Page', event: 'page_view' },
      { name: 'Form Submit', event: 'form_submitted' },
      { name: 'Lead Created', event: 'lead_created' },
    ]),
  })
})

Status State Machine

         create()
(none) ──────────> Draft

          ┌──────────┤
          │          │
          v          v
      Scheduled   Launched ←── launch()

          ┌──────────┼──────────┐
          │          │          │
          v          │          v
       Paused        │      Completed
          │          │
          v          v
       Launched   Cancelled
FromVerbTo
--createDraft
DraftupdateScheduled
Draft / ScheduledlaunchLaunched
LaunchedpausePaused
PausedlaunchLaunched
Launched / PausedcompleteCompleted
Draft / ScheduledupdateCancelled

Cross-Domain Patterns

Campaign is the bridge between marketing spend and CRM pipeline:

import { Campaign } from '@headlessly/marketing'

// When a campaign completes, compute ROI
Campaign.completed((campaign, $) => {
  const leads = await $.Lead.find({ campaign: campaign.$id })
  const deals = []
  for (const lead of leads) {
    const leadDeals = await $.Deal.find({ lead: lead.$id, stage: 'Won' })
    deals.push(...leadDeals)
  }

  const revenue = deals.reduce((sum, d) => sum + (d.value || 0), 0)
  const roi = campaign.actualCost > 0
    ? ((revenue - campaign.actualCost) / campaign.actualCost) * 100
    : 0

  await $.Campaign.update(campaign.$id, {
    actualLeads: leads.length,
    actualRevenue: revenue,
    roi,
  })

  $.Metric.create({
    name: `campaign_roi_${campaign.slug}`,
    value: roi,
    type: 'Gauge',
    unit: 'percent',
  })
})
  • CRM: Leads reference their originating campaign via Lead.campaign. Campaign owner is a Contact.
  • Analytics: Campaign launch, pause, and complete events are tracked. ROI computed as a Metric. Funnels model campaign conversion.
  • Billing: actualRevenue ties to subscription and payment data from won deals.
  • Content: Landing pages and blog posts link to campaigns. UTM parameters track attribution.
  • Forms: Forms capture leads that are attributed to campaigns.

Query Examples

SDK

import { Campaign } from '@headlessly/marketing'

// Find active campaigns
const active = await Campaign.find({
  status: { $in: ['Launched', 'Active'] },
})

// Get a specific campaign with its leads
const campaign = await Campaign.get('campaign_e5JhLzXc', {
  include: ['leads', 'owner'],
})

// Create a new email campaign
await Campaign.create({
  name: 'Product Launch Q1',
  type: 'Email',
  status: 'Draft',
  budget: 10000,
  currency: 'USD',
  targetLeads: 500,
  utmSource: 'email',
  utmMedium: 'campaign',
  utmCampaign: 'product-launch-q1',
})

// Launch it
await Campaign.launch('campaign_e5JhLzXc')

// Pause temporarily
await Campaign.pause('campaign_e5JhLzXc')

// Complete the campaign
await Campaign.complete('campaign_e5JhLzXc')

MCP

headless.ly/mcp#search
{
  "type": "Campaign",
  "filter": { "status": "Launched", "type": "Email" },
  "sort": { "launchedAt": "desc" },
  "limit": 10
}
headless.ly/mcp#fetch
{ "type": "Campaign", "id": "campaign_e5JhLzXc", "include": ["leads"] }
headless.ly/mcp#do
const campaigns = await $.Campaign.find({ status: 'Launched' })
await $.Campaign.launch('campaign_e5JhLzXc')
await $.Campaign.complete('campaign_e5JhLzXc')

REST

# List launched campaigns
curl https://marketing.headless.ly/~acme/campaigns?status=Launched

# Get a specific campaign
curl https://marketing.headless.ly/~acme/campaigns/campaign_e5JhLzXc

# Create a campaign
curl -X POST https://marketing.headless.ly/~acme/campaigns \
  -H 'Content-Type: application/json' \
  -d '{"name": "Product Launch Q1", "type": "Email", "status": "Draft", "budget": 10000}'

# Launch a campaign
curl -X POST https://marketing.headless.ly/~acme/campaigns/campaign_e5JhLzXc/launch

# Pause a campaign
curl -X POST https://marketing.headless.ly/~acme/campaigns/campaign_e5JhLzXc/pause

# Complete a campaign
curl -X POST https://marketing.headless.ly/~acme/campaigns/campaign_e5JhLzXc/complete

On this page