# Initiate Inboxmate Knowledge

> Load this before any InboxMate work — gives the full architecture: how systems connect, what IDs link what, object states, and the data flow from demo creation through email outreach. Use whenever starting InboxMate pipeline work, debugging cross-system issues, or when a skill references objects/states you don't understand.

- Skill: `psquared-development/initiate-inboxmate-knowledge` (Agent Skill)
- Install (CLI): `npx skillmds@latest add psquared-development/initiate-inboxmate-knowledge`
- Raw SKILL.md: https://api.skillmd.com/api/skills/psquared-development/initiate-inboxmate-knowledge/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: psquared-development (https://skillmd.com/u/psquared-development)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/psquared-development/initiate-inboxmate-knowledge

---


# InboxMate Architecture & Data Model

This is a context-loading skill. Read it, internalize the model, then proceed with whatever task you were doing.

## The Five Systems

InboxMate spans five independent systems. Each owns its data and exposes its own API. No system directly reads another's database — they're connected through shared IDs.

```
┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│  Twenty CRM  │     │ AgentHub DB  │     │  Notif. Svc  │
│  (GraphQL)   │     │  (Supabase)  │     │   (REST)     │
│              │     │              │     │              │
│ Opportunities│────▶│ demo_pages   │     │ email_drafts │
│ Companies    │  ↑  │ agents       │     │ email_queue  │
│ Campaigns    │  │  │ knowledge_*  │     │              │
│ People/Notes │  │  │              │     │              │
└──────────────┘  │  └──────────────┘     └──────────────┘
                  │         │                    │
                  │    serves demo               │
                  │    page content               │
                  │         ▼                     │
                  │  ┌──────────────┐             │
                  │  │  Demo Pages  │             │
                  │  │  (frontend)  │             │
                  │  │demo.inboxmate│             │
                  │  │.psquared.dev │             │
                  │  └──────────────┘             │
                  │                               │
                  │  ┌──────────────┐             │
                  └──│    Ackee     │◀────────────┘
                     │ (analytics)  │   (tracks views)
                     └──────────────┘
```

## The Key ID: How Objects Link Across Systems

The **CRM Opportunity** is the central record. Everything else hangs off it.

```
CRM Opportunity
  ├── .id              → used as crm_opportunity_id in email_drafts
  ├── .demoId          → points to demo_pages.id in AgentHub DB
  ├── .companyId       → points to CRM Company
  ├── .campaignId      → points to CRM Campaign
  └── .demoStatus      → state machine (see below)

AgentHub: demo_pages
  ├── .id              → this IS the demoId stored in CRM
  ├── .agent_id        → points to agents table
  └── .crm_opportunity_id  → backlink (often NULL, don't rely on it)

AgentHub: agents
  ├── .knowledge_bucket_ids  → uuid[] pointing to knowledge_buckets
  └── .published_at          → non-null means live

AgentHub: knowledge_buckets → knowledge_bucket_items → knowledge_bucket_chunks
  └── items have: source_type ('url' | 'knowledge_link' | 'text' | 'pdf')
  └── items have: embedding_status ('pending' | 'processing' | 'completed' | 'failed')

Notification Service: email_drafts
  ├── .crm_opportunity_id   → links back to CRM Opportunity
  ├── .crm_company_id       → links to CRM Company
  ├── .draft_type            → 'outreach' | 'followup'
  ├── .parent_draft_id       → followup points to its outreach draft
  └── .status                → 'DRAFT' | 'QUEUED' | 'SENT' | 'FAILED'
```

**Critical rule:** The CRM opportunity's `demoId` field is the *only* reliable cross-system link between CRM and AgentHub. The `demo_pages.crm_opportunity_id` column exists but is often NULL. When resolving CRM → demo page, always query CRM for `demoId`, don't query Supabase by `crm_opportunity_id`.

## Object States

### CRM Opportunity.demoStatus (the pipeline state machine)

```
                    ┌─────────────────┐
                    │ PENDING_REVIEW  │ ← demo built, awaiting QA
                    └────────┬────────┘
                     pass │     │ fail
                          ▼     ▼
               ┌────────────┐ ┌───────────┐
               │ OK_TO_SEND │ │ NEEDS_FIX │ → fix → back to PENDING_REVIEW
               └─────┬──────┘ └───────────┘
                     │ campaign + email sent
                     ▼
               ┌──────────┐
               │   SENT   │ ← outreach email sent, outreachSentAt set
               └─────┬────┘
                     │ follow-up sent
                     ▼
            ┌─────────────────┐
            │ FOLLOW_UP_SENT  │
            └─────────────────┘

Skip states (terminal): SKIP_FAULTY_WEBSITE, SKIP_IRRELEVANT, SKIPPED, DISQUALIFIED
```

### CRM Opportunity.stage

| Stage | Meaning |
|---|---|
| `SCREENING` | Outreach phase (all demo pipeline work happens here) |
| `MEETING` | Prospect responded / call scheduled |
| `PROPOSAL` | Quoted or decision-maker demo |
| `CUSTOMER` | Paying customer |

### Email Draft.status

```
DRAFT → (schedule) → QUEUED → (send job) → SENT
                                         → FAILED
```

Follow-up drafts are created alongside initial outreach (linked via `parent_draft_id`), with `send_after_days` controlling when they queue.

### Campaign Structure

Campaigns group opportunities into sendable batches. A campaign has `offerExpiresAt` and `offerText` which are injected into demo pages dynamically (not stored on `demo_pages` directly — the API merges them at read time with a 5-min cache).

## Data Flow: Demo Creation → Email Outreach

### 1. Lead Found → CRM
- Company + Person created in CRM
- No opportunity yet

### 2. Demo Built → AgentHub + CRM
- Agent created via MCP (`create_agent`)
- Knowledge bucket created, URLs scraped, items embedded
- Demo page created via MCP (`create_demo_page`) → returns `demo_pages.id`
- CRM Opportunity created at stage=SCREENING, demoStatus=PENDING_REVIEW
  - `demoId` = the demo page UUID
  - `demoUrl` = `https://demo.inboxmate.psquared.dev/?id=<demoId>`

### 3. QA Review → CRM status change
- `/review-demos` fetches PENDING_REVIEW opportunities
- Checks agent, colors, knowledge, greeting
- Sets demoStatus to OK_TO_SEND or NEEDS_FIX

### 4. Campaign Planned → CRM
- `/plan-campaign` creates a CRM Campaign
- Links selected opportunities via `campaignId`
- Sets `offerText` and `offerExpiresAt` on the campaign

### 5. Email Drafts Created → Notification Service
- `/setup-email-drafts` queries OK_TO_SEND opportunities
- Creates outreach draft + follow-up draft pair per opportunity
- Drafts stored with `crm_opportunity_id` for back-linking

### 6. Emails Scheduled & Sent → Notification Service + CRM
- Admin schedules drafts → status: QUEUED, enters `email_queue`
- Send job fires → status: SENT, CRM updated to demoStatus=SENT
- Follow-up auto-queues based on `send_after_days`

### 7. Prospect Claims Demo → AgentHub
- Prospect clicks CTA on demo page
- Claim flow: clones agent + knowledge to new account
- Marks `demo_pages.claimed = true`
- Sends Telegram notification + welcome email
- Updates CRM stage/status

## API Quick Reference

| System | Endpoint | Auth Token |
|---|---|---|
| CRM | `POST https://crm.psquared.dev/graphql` | `$PSQUARED_CRM_TOKEN` |
| MCP | `POST https://app.psquared.dev/api/mcp` | `$NUXT_MCP_DEMO_TOKEN` |
| Demo API | `GET https://app.psquared.dev/api/demo/<id>` | None (public) |
| Notif. Svc | `https://notifications.psquared.dev/drafts/*` | `$EMAIL_DRAFT_ONLY_BEARER` |
| Sanity Check | `POST https://notifications.psquared.dev/drafts/sanity-check` | `$EMAIL_DRAFT_ONLY_BEARER` |
| Ackee | `POST https://ackee.psquared.dev/api` | `$ACKEE_TOKEN` |
| Supabase | `mcp__plugin_supabase_supabase__execute_sql` | project: `fevtfywriufbqnvbgyrm` |

## Sanity Check

The notification service exposes a sanity check that validates demo agent health. Accepts three input modes:

```json
// By opportunity IDs
{ "crm_opportunity_ids": ["uuid", ...] }

// By campaign
{ "campaign_id": "uuid" }

// By draft IDs
{ "draft_ids": ["uuid", ...] }
```

Returns per-item: `healthy` boolean + `issues` array + `warnings` array. Checks:
- **Health failures (unhealthy):** demo/agent not found, not published, empty buckets, text-type items, failed/processing embeddings, 404 content detected, >50% items under 50 tokens
- **Warnings (non-blocking):** missing source URLs, some low-token items, thin avg tokens, claimed demos
- **Also checks:** demo API reachability, email draft link correctness

Response includes `demo_page_id` for cross-system debugging, and `avg_tokens` for knowledge quality assessment.

## Common Gotchas

1. **CRM uses inline IDs, not parameterized variables.** Twenty CRM's GraphQL does NOT support `$id: ID!` parameters. Always inline UUIDs directly in the query string.

2. **Don't `source .env`** — values contain semicolons. Use `Read` tool or `grep` to extract specific values.

3. **MCP token is `NUXT_MCP_DEMO_TOKEN`** (not `INBOXMATE_DEMO_MCP_TOKEN`). Some older skills use the wrong name.

4. **`knowledge_link` = URL-sourced.** In `knowledge_bucket_items.source_type`, items scraped from URLs are stored as `knowledge_link`, not `url`. Both are valid URL-sourced content.

5. **Campaigns manage deadlines, not individual demos.** Never set `offerExpiresAt` or `offerText` directly on `demo_pages` — these come from the campaign and are merged dynamically at API read time.

6. **Follow-up emails: never mention "follow-up" in the subject.** Body must be unique, not a generic template. Always include `variables` in PUT body when updating drafts.

7. **Never guess URLs for scraping.** Always discover real pages from the homepage navigation first. Generic patterns (`/kontakt/`, `/ueber-uns/`, `/leistungen/`) fail on 60%+ of sites.

8. **Tavily failures → WebFetch fallback.** `scrape_and_build_knowledge` uses Tavily. Some sites block it (all URLs timeout). When this happens, use WebFetch to scrape pages manually and add via `add_to_bucket` with `sourceUrl`. Never leave a bucket empty.

9. **Skip junk URLs:** `/wp-json/`, `/xmlrpc.php`, `/feed/`, `/favicon.ico`, `.webp`, `.ico` — these produce garbage knowledge items.

10. **Content limit is 50K characters** per knowledge item. Tavily advanced mode can return large pages — they won't be truncated.

11. **404 pages are auto-detected** by `scrape_and_build_knowledge` — pages with "nicht gefunden"/"not found"/"404" in the title are rejected and returned in the `failed` array. No need to check manually.

## What Skill Do I Need?

Based on what you're trying to do, use:

| Goal | Skill |
|------|-------|
| Need new leads | `/find-leads [N]` |
| Create demos for CRM leads | `/inboxmate-batch-demo` or `/inboxmate-demo <company>` |
| QA existing demos | `/review-demos` |
| Fix flagged demos | `/fix-demos` |
| Check if demos are healthy | `/sanity-check` |
| Group demos into a campaign | `/plan-campaign` |
| Create outreach emails | `/setup-email-drafts [campaignId]` |
| Edit email drafts | `/refine-email-drafts` |
| Check how outreach is doing | `/check-outreach-status` |
| Full funnel analytics | `/check-demo-analytics` |
| Upgrade demo knowledge | `/refurbish-demos` |
| Change pricing | `/price-change` |
| Deep debugging / raw queries | `/analyse-inboxmate` |

### Pipeline Order

If starting from scratch, follow this sequence:
```
/find-leads → /inboxmate-batch-demo → /review-demos → /fix-demos (if needed)
→ /sanity-check → /plan-campaign → /setup-email-drafts → /refine-email-drafts (if needed)
→ 👤 send → /check-outreach-status → /check-demo-analytics
```

