๐Ÿง  HeyCMO
Features

Dogfooding (Customer

heycmo runs heycmo's own marketing through the same agent infrastructure customers use โ€” same caps, same approval queue, same provenance.

Dogfooding (Customer #0)

heycmo is its own first customer. The marketing site, the social channels, the cold email sequences, the LinkedIn outreach โ€” all of it is published, scheduled, and replied to by the same agents customers run, against the same database tables, with the same caps, the same approval queue, and the same provenance footers.

It's not a demo tenant. It's the real thing. If engagement-response breaks, our X mentions go unanswered. If cold-email-sequence over-sends, we land in spam. The product gets pressure-tested before customers ever see the regression.

Configuration

PropertyValue
Phase1.1 (seeding) + 1.2 (lead-outreach drain)
Constants moduleapps/api/agent/lib/heycmo-tenant.ts
Seed scriptprisma/seeds/heycmo-tenant.ts
Brand kernel sourcecontent/heycmo-brand-kernel.json
Public dashboard/built-by-heycmo
IdentityA single Customer row with id = HEYCMO_CUSTOMER_ID

The constants

// apps/api/agent/lib/heycmo-tenant.ts

export const HEYCMO_CUSTOMER_ID  = '00000000-0000-4000-8000-000000000001';
export const HEYCMO_TENANT_EMAIL = 'agents@heycmo.ai';
export const HEYCMO_BRAND_NAME   = 'HeyCMO';
export const HEYCMO_WEBSITE      = 'https://heycmo.ai';

The UUID is frozen. It's referenced by run logs, content rows, and public URLs. If this value changes, every heycmo-owned row needs to be migrated โ€” not something to do casually.

The type guard

export function isHeycmoTenant(customerId: string | null | undefined): boolean {
  return customerId === HEYCMO_CUSTOMER_ID;
}

Used by code paths that need to branch on "are we publishing for heycmo itself" โ€” for example, tighter brand-compliance checks, the run-ID provenance footer, stricter approval gates, exclusion from customer-cohort metrics.

Seeding

prisma/seeds/heycmo-tenant.ts:

npx tsx prisma/seeds/heycmo-tenant.ts

The script is idempotent โ€” safe to run multiple times. It uses upsert on the canonical HEYCMO_CUSTOMER_ID so re-running won't create duplicates and won't clobber an existing brand kernel edited via the dashboard.

What it creates:

  • A Customer row with id = HEYCMO_CUSTOMER_ID, plan = 'heycmo' (internal plan, not Stripe-billed), status = 'active'.
  • A CustomerConfig row with configType = 'brand_profile' populated from content/heycmo-brand-kernel.json โ€” kept in JSON-on-disk so brand changes are code-reviewable in PRs rather than mutated in the DB silently.
  • A Brand row with isDefault = true.
  • A new API key (printed once at the end of the run if no existing key was found).

What it doesn't create:

  • Composio integration rows โ€” those need real OAuth flows. Wire them via the dashboard after seeding.
  • Stripe subscription โ€” heycmo is internal-billed.
  • Sessions โ€” admin signs in via the API key just like any other tenant.

After running, the Inngest cron fan-out at apps/api/agent/inngest.ts automatically picks up HeyCMO in every per-customer schedule. No code changes needed.

What heycmo's agents do for heycmo

Same things they do for paying customers:

  • Riley (seo-writer) publishes blog posts on heycmo.ai
  • Avery (social-manager) publishes cross-channel posts on X, LinkedIn, Facebook, Instagram
  • Sam (engagement) replies to mentions, comments, and DMs
  • Mia (lead-gen) drafts outreach for high-confidence lead signals
  • Jordan (email-marketer) runs cold email sequences from agents@heycmo.ai
  • Phoenix (ads-manager) runs ads
  • Quinn (tools-page-builder) ships free interactive tools at /tools/<slug>
  • Sage (analyst) writes the weekly performance report
  • Drew (cro-specialist) audits the conversion funnel

Every one of those actions runs through the same approval queue, hits the same cold_email_sends / linkedin_outreach_queue tables, and respects the same caps. The only difference is the customer ID.

The lead-outreach drain (Phase 1.2)

Closes the engagement โ†’ outreach loop. This is a Mastra workflow at apps/api/agent/workflows/lead-outreach.ts, scheduled every 15 minutes (matches the engagement-check cadence).

Why it exists

Pre-this-workflow, the X stream router persisted high-confidence x_stream_lead_signal events into the events table โ€” but those events accumulated in a dead-letter queue with no automated consumer. The product was detecting leads brilliantly and had no way to act on them.

What it does

  1. Atomically claims unconsumed x_stream_lead_signal events via db.events.claimAndProcessXStreamEvents โ€” the same exactly-once FOR UPDATE SKIP LOCKED pattern that engagement-response uses.
  2. Enriches each lead via Mia's searchLeadIntel tool.
  3. Mia scores fit + intent and drafts an outreach message.
  4. Pushes the draft into the approval queue as a ContentPiece with status = 'draft'. The customer reviews + approves before any send happens.

What it explicitly doesn't do

  • No actual cold-email send โ€” that's Phase 2.1's cold-email-sequence workflow, kicked off after approval.
  • No Reddit / LinkedIn outreach send โ€” those are Phases 2.2 / 3.1.
  • No auto-approve mode โ€” the approval queue is the gate. Conservative-by-default, per the master plan's hard rule.

Idempotency

claimAndProcessXStreamEvents wraps the whole thing in a Postgres transaction. Concurrent runs see disjoint event sets. If the processor throws, the transaction rolls back and the events become available again on the next tick.

Schemas

leadOutreachInputSchema = {
  resourceId?: string,
  customerId?: string,
  lookbackHours?: number,    // 1-168, default 24
  limit?: number,            // 1-50, default 20
}

leadOutreachOutputSchema = {
  customerId: string,
  claimed: number,            // events locked
  drafted: number,            // outreach drafts created
  consumed: number,           // events marked consumed
  skipped: number,            // claimed but not drafted (low score / errors)
  summary: string,
}

Why the 50-event hard ceiling: beyond that, the workflow risks exhausting Mia's token budget and producing low-quality drafts.

Why the 24-hour lookback: leads older than 24h are stale โ€” the prospect's situation has likely changed and the outreach context will be wrong.

provenanceFooter({ agentName, agentRole, runId }) returns a one-line plain-text footer appended to every public post heycmo's agents publish on heycmo's own channels:

โ†ณ Built by Sam (engagement) ยท run #r_abc123 ยท heycmo.ai/built-by-heycmo/r_abc123

It surfaces the run ID so a skeptic can verify against /built-by-heycmo. Customer tenants get the rich <ProvenanceFooter /> component on their own published content; heycmo's own posts use this minimal text version because they go to platforms (X, LinkedIn) where rich HTML isn't an option.

The /built-by-heycmo dashboard

The public dogfooding feed at /built-by-heycmo. The pitch: we use heycmo to grow heycmo, here's the receipts.

It pulls from /api/public/built-by-heycmo (no auth, IP-rate-limited) and surfaces:

  • Recently published agent-generated content with run IDs + URLs
  • Recent agent activity events (mentions, leads, milestones)
  • Per-agent counters

Every row links to a verifiable artifact. Visit the URL, see the post, check the run ID. No mockups, no fake data.

See Provenance & RSS for the full public-artifact stack.

Why this pattern matters

Three benefits, in order of importance:

  1. Pressure-tests the product on a live workload. Bugs in caps, in approval gates, in suppression lists โ€” they hit us first, in production, on the real heycmo brand. Customer outages get prevented because we noticed our own outage.

  2. Generates the strongest sales argument we have. "Show me the agent in action" is answered with a URL, a run ID, and a 90-day window of receipts. Demos lie; live feeds don't.

  3. Forces dogfooding-grade UX. If the in-product flow has friction, our team feels it daily. The friction gets fixed.

Source files

  • apps/api/agent/lib/heycmo-tenant.ts โ€” HEYCMO_CUSTOMER_ID, isHeycmoTenant, provenanceFooter()
  • prisma/seeds/heycmo-tenant.ts โ€” the idempotent seeding script
  • content/heycmo-brand-kernel.json โ€” heycmo's own brand kernel (PR-reviewable)
  • apps/api/agent/workflows/lead-outreach.ts โ€” Phase 1.2 drain workflow
  • apps/api/agent/workflows/__tests__/lead-outreach.test.ts โ€” test coverage
  • apps/api/agent/inngest.ts โ€” cron fan-out (auto-includes HeyCMO once seeded)

On this page