Skip to content

Marketing Context

The Marketing context manages marketing campaigns, promotions, and advertising across multiple channels. It integrates with the Loyalty context for loyalty-bonus promotions.

Purpose

A 500-location restaurant chain runs promotional campaigns across email, social media, in-store signage, the mobile app, and the website. The Marketing context provides a structured workflow for creating, scheduling, launching, pausing, and ending campaigns — with built-in support for promotional offers.

Interview Connection

From the CEO's interview:

"I tried to get them to build a loyalty program a couple of years ago."

The Marketing context works alongside the Loyalty context. Campaigns can include LoyaltyBonus promotions that offer bonus points, connecting marketing efforts directly to customer retention.

Types

type CampaignId is Id(Marketing.Campaign) with {
  briefly "Campaign identifier"
  described by "Unique identifier for a marketing campaign."
}

type CampaignStatus is any of {
  CampaignDraftStatus,
  CampaignScheduledStatus,
  CampaignLiveStatus,
  CampaignPausedStatus,
  CampaignEndedStatus
} with {
  briefly "Campaign status"
  described by "Current status of a marketing campaign."
}

type CampaignChannel is any of {
  Email,
  SocialMedia,
  InStore,
  MobileApp,
  Website
} with {
  briefly "Campaign channel"
  described by "Marketing channel for the campaign."
}

type PromotionType is any of {
  PercentDiscount,
  FixedDiscount,
  BuyOneGetOne,
  FreeItem,
  LoyaltyBonus
} with {
  briefly "Promotion type"
  described by "Type of promotional offer."
}

type CampaignPromotion is {
  promotionType is PromotionType
  promotionValue is optional Decimal(8, 2)
  promotionDescription is String(1, 500)
  applicableItems is many optional String(1, 50)
} with {
  briefly "Campaign promotion"
  described by "A promotional offer within a campaign."
}

The CampaignChannel enumeration captures the five channels a campaign can target. The CampaignPromotion record type bundles the promotion details, including which menu items it applies to.

Entity: Campaign

The Campaign entity has a 5-command lifecycle:

entity Campaign is {

  command CreateCampaign is {
    campaignId is CampaignId
    campaignName is String(1, 200)
    campaignDescription is String(1, 2000)
    campaignChannels is many CampaignChannel
    campaignPromotion is optional CampaignPromotion
  }

  command ScheduleCampaign is {
    campaignId is CampaignId
    campaignStartDate is Date
    campaignEndDate is Date
  }

  command LaunchCampaign is {
    campaignId is CampaignId
    launchedAt is TimeStamp
  }

  command PauseCampaign is {
    campaignId is CampaignId
    pauseReason is String(1, 500)
  }

  command EndCampaign is {
    campaignId is CampaignId
    endedAt is TimeStamp
  }

  // Events: CampaignCreated, CampaignScheduled, CampaignLaunched,
  //         CampaignPaused, CampaignEnded

  state ActiveCampaign of Campaign.CampaignStateData

  handler CampaignHandler is {
    on command CreateCampaign {
      morph entity Marketing.Campaign to state
        Marketing.Campaign.ActiveCampaign
        with command CreateCampaign
      tell event CampaignCreated to
        entity Marketing.Campaign
    }
    on command ScheduleCampaign {
      tell event CampaignScheduled to
        entity Marketing.Campaign
    }
    on command LaunchCampaign {
      tell event CampaignLaunched to
        entity Marketing.Campaign
    }
    on command PauseCampaign {
      tell event CampaignPaused to
        entity Marketing.Campaign
    }
    on command EndCampaign {
      tell event CampaignEnded to
        entity Marketing.Campaign
    }
  }
}

The lifecycle: Create → Schedule → Launch → (optional Pause) → End.

Note that campaignChannels uses many CampaignChannel — a single campaign can target multiple channels simultaneously. The campaignPromotion is optional because not every campaign includes a promotional offer; some are purely awareness campaigns.

Repository

repository CampaignRepository is {
  schema CampaignData is relational
    of campaigns as Campaign
    index on field Campaign.campaignId
    index on field Campaign.campaignStatus
}

Design Decisions

Why separate from Menu Management? Marketing campaigns and menu releases operate on different timelines, involve different stakeholders, and have different lifecycles. A marketing campaign might promote existing menu items without any menu changes. Keeping them separate means the marketing team doesn't need to coordinate with the menu workflow.

Why include LoyaltyBonus in PromotionType? This connects marketing campaigns directly to the loyalty program. A campaign can offer "Double Points Weekend" by specifying a LoyaltyBonus promotion type. The Loyalty context processes the bonus points independently.

Multi-channel support: The many CampaignChannel field means a campaign targets specific channels. This enables channel-specific analytics and allows campaigns to be launched on some channels before others (e.g., email first, then social media).

Source