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
| Property | Value |
|---|---|
| Phase | 1.1 (seeding) + 1.2 (lead-outreach drain) |
| Constants module | apps/api/agent/lib/heycmo-tenant.ts |
| Seed script | prisma/seeds/heycmo-tenant.ts |
| Brand kernel source | content/heycmo-brand-kernel.json |
| Public dashboard | /built-by-heycmo |
| Identity | A 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.tsThe 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
Customerrow withid = HEYCMO_CUSTOMER_ID,plan = 'heycmo'(internal plan, not Stripe-billed),status = 'active'. - A
CustomerConfigrow withconfigType = 'brand_profile'populated fromcontent/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
Brandrow withisDefault = 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 fromagents@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
- Atomically claims unconsumed
x_stream_lead_signalevents viadb.events.claimAndProcessXStreamEventsโ the same exactly-onceFOR UPDATE SKIP LOCKEDpattern thatengagement-responseuses. - Enriches each lead via Mia's
searchLeadInteltool. - Mia scores fit + intent and drafts an outreach message.
- Pushes the draft into the approval queue as a
ContentPiecewithstatus = '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-sequenceworkflow, 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.
The provenance footer for heycmo's own posts
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_abc123It 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:
-
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.
-
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.
-
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 scriptcontent/heycmo-brand-kernel.jsonโ heycmo's own brand kernel (PR-reviewable)apps/api/agent/workflows/lead-outreach.tsโ Phase 1.2 drain workflowapps/api/agent/workflows/__tests__/lead-outreach.test.tsโ test coverageapps/api/agent/inngest.tsโ cron fan-out (auto-includes HeyCMO once seeded)