{
  "id": "2026-04-29-brooke-sales-campaigns-5cbb26647e",
  "scope": "redkey",
  "source_of_truth": "repo",
  "source_path": "docs/specs/2026-04-29-brooke-sales-campaigns.md",
  "source_kind": "markdown",
  "visibility": "internal",
  "renderer_id": "design_doc.dreamborn-forge.generated.v1",
  "design_system": "dreamborn-design-system:forge",
  "generated_at": "2026-05-09T13:00:55.695Z",
  "artifact_type": "design_doc",
  "schema_version": "design_doc.generated.v1",
  "title": "Brooke / roles.sales — Campaign & Sequence System Design",
  "summary": "Brooke / roles.sales — Campaign & Sequence System Design Date: 2026 04 29 Status: Approved for planning Overview A campaign and sequence system for the B2BEA / RedKey sales motion. Contacts are enrolled in campaigns by the roles.sales agent (Brooke) after Justin gates the initial routing. Each campaign has its own sequence of timed touchpoints (email + Linke...",
  "format_source": "markdown",
  "sections": [
    {
      "title": "Brooke / roles.sales — Campaign & Sequence System Design",
      "level": 1,
      "body": "**Date:** 2026-04-29  \n**Status:** Approved for planning\n\n---"
    },
    {
      "title": "Overview",
      "level": 2,
      "body": "A campaign and sequence system for the B2BEA / RedKey sales motion. Contacts are enrolled in campaigns by the `roles.sales` agent (Brooke) after Justin gates the initial routing. Each campaign has its own sequence of timed touchpoints (email + LinkedIn). When a contact exits a campaign, the campaign's `on_exit` map determines the next campaign automatically — Justin only gates the first handoff.\n\n---"
    },
    {
      "title": "The Full Pipeline",
      "level": 2,
      "body": "```\nTara (roles.bdr) finds prospect → books meeting\n  → meeting_booked task dispatched to roles.exec (Justin)\n  → Justin reviews handoff, specifies campaign(s): \"vendor_membership_close\"\n  → task dispatched to roles.sales\n  → roles.sales enrolls contact in specified campaign(s), step 1\n  → Engine fires execute_sequence_step tasks as next_send_at arrives\n  → roles.sales drafts each touch → staged_drafts → Justin approves → sends\n  → reply received → roles.sales classifies → continue or exit\n  → exit: on_exit map routes to next campaign automatically\n```\n\n---"
    },
    {
      "title": "BDR Campaigns (Tara / roles.bdr)",
      "level": 3,
      "body": "| Slug | Type | Client | Purpose |\n|---|---|---|---|\n| `b2bea_world` | event | b2bea | Outreach for B2B eCommerce World event |\n| `b2bea_membership` | membership | b2bea | Vendor membership outreach |\n| `dreamborn` | platform | dreamborn | DreamBorn platform outreach |"
    },
    {
      "title": "Sales Campaigns (Brooke / roles.sales)",
      "level": 3,
      "body": "| Slug | Type | Client | Purpose |\n|---|---|---|---|\n| `b2bea_membership_close` | membership | b2bea | Post-meeting close sequence |"
    },
    {
      "title": "Customer Success Campaigns",
      "level": 3,
      "body": "| Slug | Type | Client | Purpose |\n|---|---|---|---|\n| `b2bea_membership_cs` | onboarding | b2bea | Post-close member onboarding |"
    },
    {
      "title": "on_exit Chains",
      "level": 3,
      "body": "```\nb2bea_membership (BDR) → meeting → Justin gates → b2bea_membership_close\nb2bea_membership_close → converted  → b2bea_membership_cs\nb2bea_membership_close → no_engagement → null (close the loop)\nb2bea_membership_close → rejected   → null (close the loop)\nb2bea_world (BDR)       → registered → null (no sales sequence for event)\ndreamborn (BDR)         → meeting   → Justin gates → no sales campaign yet\n```\n\n---"
    },
    {
      "title": "`campaigns` table",
      "level": 3,
      "body": "```sql\nid          uuid primary key\nslug        text unique not null               -- e.g. 'b2bea_membership_close'\nname        text not null\nclient_id   text not null\ntype        text not null                      -- event | membership | platform | onboarding\nowner_role  text not null                      -- roles.bdr | roles.sales | roles.cs\nstatus      text not null default 'active'    -- active | paused | archived\non_exit     jsonb not null default '{}'\n  -- shape: { \"converted\": \"<slug|null>\", \"no_engagement\": \"<slug|null>\", \"rejected\": \"<slug|null>\" }\ncreated_at  timestamptz not null default now()\n```"
    },
    {
      "title": "`campaign_sequences` table",
      "level": 3,
      "body": "Each row is one step in a campaign's sequence.\n\n```sql\nid                   uuid primary key\ncampaign_id          uuid not null references campaigns(id)\nstep_number          int not null                          -- 1-based, per campaign\ndelay_days           int not null                          -- days from enrollment (step 1 = day 0)\nchannel              text not null                         -- email | linkedin\ncontent_type         text not null\n  -- follow_up | proof | boss_arming | campaign_visibility\n  -- personal_visibility | marketing_relief | close | exit\ntemplate_instructions text not null                        -- what to write — not the copy itself\nunique (campaign_id, step_number)\n```"
    },
    {
      "title": "`campaign_contacts` table",
      "level": 3,
      "body": "One row per contact per campaign enrollment. Each enrollment starts fresh at step 1.\n\n```sql\nid              uuid primary key\ncontact_id      text not null                   -- Attio contact record ID\ncampaign_id     uuid not null references campaigns(id)\nclient_id       text not null\nstatus          text not null default 'active'  -- active | converted | rejected | timed_out\ncurrent_step    int not null default 1\nnext_send_at    timestamptz                     -- when to fire the next step\nenrolled_at     timestamptz not null default now()\nexited_at       timestamptz\nexit_reason     text\ngoal            text                            -- awareness | pipeline | credibility | marketing_relief\nnotes           text                            -- from Justin's exec gate routing decision\n-- partial unique: only one ACTIVE enrollment per contact per campaign\n-- exited enrollments are preserved as history and do not block re-enrollment\n-- create unique index campaign_contacts_active_uniq on campaign_contacts (contact_id, campaign_id) where status = 'active'\n```"
    },
    {
      "title": "`sales_send_queue` table",
      "level": 3,
      "body": "Individual outbound items queued for execution.\n\n```sql\nid               uuid primary key\nclient_id        text not null\ncontact_id       text not null\ncampaign_id      uuid not null references campaigns(id)\nstep_number      int not null\nchannel          text not null                  -- email | linkedin\nstaged_draft_id  uuid                           -- FK to staged_drafts once drafted\nscheduled_at     timestamptz not null\nsent_at          timestamptz\nstatus           text not null default 'pending'  -- pending | drafted | approved | sent | failed\nerror            text\ncreated_at       timestamptz not null default now()\n```\n\n---"
    },
    {
      "title": "roles.sales Task Types",
      "level": 2,
      "body": "| Task type | Trigger | What roles.sales does |\n|---|---|---|\n| `enroll_contact` | Justin's exec gate approval | Creates `campaign_contacts` row(s), sets step 1, calculates first `next_send_at` |\n| `execute_sequence_step` | Engine when `next_send_at` arrives | Drafts the touch for current step, writes to `staged_drafts`, advances `current_step`, sets next `next_send_at` |\n| `process_reply` | Inbound reply received (deferred — reply detection mechanism TBD) | Classifies intent, updates Attio activity, continues sequence or triggers exit |\n| `exit_contact` | Exit condition met | Sets `campaign_contacts.status`, follows `on_exit` map to enroll in next campaign |\n\n---"
    },
    {
      "title": "Sequence Execution Rules",
      "level": 2,
      "body": "- When a contact is enrolled, `current_step = 1` always — per-campaign, not global\n- `next_send_at` = `enrolled_at` + `campaign_sequences[step=1].delay_days`\n- After each step executes: `current_step++`, `next_send_at` = `enrolled_at` + `campaign_sequences[next_step].delay_days`\n- If no next step exists after the final step: trigger `exit_contact` with reason `no_engagement`\n- Per-contact daily send cap: max 1 touch per day across all active campaigns for a given contact (Engine checks before dispatching `execute_sequence_step`)\n\n---"
    },
    {
      "title": "Attio Integration",
      "level": 2,
      "body": "Attio is the record of truth for contacts and deals. Supabase holds operational execution state.\n\n**Attio writes (by roles.sales):**\n- Create/update contact record on `enroll_contact`\n- Log every touch as a contact activity (channel, content_type, date)\n- Create deal record at `enroll_contact` for sales campaigns\n- Update deal stage at each phase transition\n- Close deal on `exit_contact` (won or lost)\n\n**Supabase holds:**\n- Campaign definitions and sequences\n- Enrollment state (`campaign_contacts`)\n- Send queue (`sales_send_queue`)\n- `staged_drafts` (all outbound, pending Justin's approval)\n\n---"
    },
    {
      "title": "staged_drafts Format (sales touches)",
      "level": 2,
      "body": "```json\n{\n  \"channel\": \"email | linkedin\",\n  \"to_id\": \"<email address or LinkedIn URN>\",\n  \"body\": \"<message — Justin's voice>\",\n  \"context\": {\n    \"contact_id\": \"<Attio contact ID>\",\n    \"contact_name\": \"<string>\",\n    \"company\": \"<string>\",\n    \"campaign_slug\": \"<string>\",\n    \"step_number\": \"<int>\",\n    \"content_type\": \"<string>\",\n    \"goal\": \"<awareness | pipeline | credibility | marketing_relief>\",\n    \"requires_justin_send\": \"<bool — true for close step, Justin sends personally>\"\n  }\n}\n```\n\n---"
    },
    {
      "title": "b2bea_membership_close Sequence (initial)",
      "level": 2,
      "body": "| Step | Day | Channel | Content type | What to write |\n|---|---|---|---|---|\n| 1 | 0 | email | follow_up | Personal follow-up same day as meeting. Reference what they said specifically. Grant pre-sale access to B2BEA site + training content. |\n| 2 | 3 | email | proof | Goal-specific proof asset. Awareness → draft a LinkedIn post they could publish today. Credibility → mock up how they'd appear in an upcoming report. Pipeline → ICP overlap in B2BEA community. Marketing relief → content calendar for their first 90 days. |\n| 3 | 7 | email | boss_arming | \"Internal case\" kit — ROI framing, peer proof (which competitors/peers are members), deliverables list. Written so they can forward it to their boss. |\n| 4 | 10 | linkedin | follow_up | Warm touch. Different angle. About them personally, not B2BEA. |\n| 5 | 14 | email | campaign_visibility | Upcoming B2BEA reports + who's already confirmed. FOMO is information, not pressure. |\n| 6 | 21 | email | personal_visibility | Career upside angle. \"We'd put YOU on video — your name and face.\" Address the marketing lead specifically. |\n| 7 | 35 | email | close | \"Let's turn this on fully.\" Natural continuation of what's already happening, not a transaction. Flag Justin to send personally. |\n| 8 | 56 | email | exit | 60-day exit. One final frame of what they're stepping away from — specific upcoming campaigns, content opportunities relevant to their goal. |\n\n---"
    },
    {
      "title": "Role Topic Changes",
      "level": 2,
      "body": "Tara moves from `roles.sales` to `roles.bdr`. Brooke owns `roles.sales` exclusively.\n\n- `roles.bdr` — new topic required (HCS topic to be created)\n- `roles.sales` — existing topic `0.0.8796955`, re-assigned to Brooke\n- `agents.traci` — update topic map entry to reflect BDR role\n- `agents.brooke` — new `agents.brooke` HCS topic required\n\n---"
    },
    {
      "title": "Out of Scope",
      "level": 2,
      "body": "- DreamBorn sales campaign sequence (deferred — Justin gates manually for now)\n- Automated campaign enrollment without Justin's exec gate (deferred)\n- Brooke sending autonomously without staged_drafts approval (never)\n- Customer success sequence content (defined separately)\n- Attio MCP plugin build (separate task)"
    }
  ],
  "html_path": "artifacts/2026-04-29-brooke-sales-campaigns-5cbb26647e.html",
  "json_path": "artifacts/2026-04-29-brooke-sales-campaigns-5cbb26647e.json"
}