# PRODUCT REQUIREMENTS DOCUMENT — AS-BUILT
# One-Command Lead Magnet Factory

**Version**: 1.0  
**Type**: AI-Assisted Developer Tool / Marketing Automation System  
**Status**: Production  
**Language**: This system is designed for Spanish-language audiences, but the architecture is language-agnostic.

---

## 1. Executive Summary

**Product Name**: One-Command Lead Magnet Factory  
**Current Status**: Production — actively used by operator  
**Product Type**: Internal developer tool + marketing automation system  
**Primary Objective**: Collapse the entire process of creating and publishing a lead magnet — research, copywriting, landing page, delivery email, deploy, and distribution — from ~6 hours of manual work to ~15 minutes using two AI commands  
**Current Users**: Single operator (the person who owns the brand and email list)  
**Business Context**: Content creators, consultants, and solo entrepreneurs who publish lead magnets to grow their email lists need a repeatable, high-quality production system that doesn't require design, dev, or copywriting skills for each new piece  
**Technical Maturity**: Production — fully deployed, multi-lead-magnet inventory, zero manual deploy steps

---

## 2. Problem Statement

**User Problem**: Creating a quality lead magnet requires chaining 7 distinct disciplines: topic research, brand-voice copywriting, landing page design and development, delivery email configuration, ESP/CRM integration, deployment to production, and public distribution. Done manually, each lead magnet takes ~6 hours and produces inconsistencies — different visual styles, varying copy voice, forgotten steps (email never configured, resource never listed in the directory).

**Business Problem**: The high per-unit production cost limits publication cadence, and cadence limits email list growth. The system should make producing a new lead magnet as easy as writing a one-line command.

**Current Impact**: Without automation, a creator would need to either outsource all 7 steps (expensive), do them manually (slow and inconsistent), or produce lead magnets rarely (limited list growth).

**Evidence**:
- Decision: After building the first 2-3 lead magnets manually, the operator codified the process into a command-driven system
- Evidence: The system currently manages 11+ live lead magnets with zero individual deploy steps after the initial commit
- Assumption: A system that enforces visual and copy rules automatically produces higher-converting pages than ad-hoc production

**Assumptions**:
- Operator has basic git knowledge
- Operator uses Vercel for hosting
- Operator uses Brevo (formerly Sendinblue) as the ESP
- Operator uses Notion as the content pipeline and review interface
- Operator uses Claude Code (Anthropic's CLI) as the AI coding agent

---

## 3. Target Users

**Primary Persona**: The Operator  
A solo content creator, consultant, or entrepreneur who owns a brand, publishes lead magnets to grow an email list, and wants to produce new resources frequently without code or design work on each iteration.  
- Context: Works alone or with a very small team  
- Technical level: Comfortable with git and CLI, not necessarily a developer  
- Frequency: Wants to publish 1-4 new lead magnets per month  
- Jobs to Be Done:
  - Publish a new lead magnet without touching HTML or CSS
  - Ensure every landing page looks and sounds like the brand
  - Get the delivery email working automatically on deploy
  - Have new resources appear in the public directory without editing the main site

**Secondary Persona**: The Subscriber  
A visitor who arrives at a lead magnet landing page, opts in with their email, and receives the resource automatically.  
- Context: Arrives via social, referral link, or the public resource directory  
- Jobs to Be Done:
  - Understand quickly what they're getting and whether it's worth their email
  - Submit the form and receive the resource without friction

**Admin/Internal Users**: None beyond the operator

**Secondary Use Case**: The System Cloner  
A developer or creator who forks this system and adapts it for their own brand. The architecture is designed to be white-labeled — replacing brand variables, API keys, and copy voice rules is the only customization required.

---

## 4. Current Product Overview

**Core Capabilities**:
- `/lead-magnet [topic]` command: researches the topic, writes lead magnet content, creates a Notion page with full formatted content, and registers the item in the pipeline DB with status "Draft"
- `/execute-lead-magnets` command: reads all pipeline items with status "Execute", generates the HTML landing page, writes the delivery email, registers it in the email JSON index, updates Notion, commits, and pushes to production
- Live resource directory: a public-facing HTML page that fetches the manifest JSON at runtime and renders all lead magnets with topic tags, difficulty level badges, search, and filter
- Automatic email delivery: when a subscriber submits a form, a serverless function registers them in Brevo and sends the delivery email using the pre-written copy stored in the JSON index
- Reverse proxy: the main brand domain proxies to the engine without exposing the underlying service URL

**Core Workflows**:
1. Operator writes `/lead-magnet [topic]` → Claude researches, writes content, creates Notion page, registers in pipeline
2. Operator reviews and edits content in Notion, changes status to "Execute"
3. Operator writes `/execute-lead-magnets` → Claude generates landing page + email, deploys
4. Subscriber visits landing page, submits form → serverless function fires → Brevo contact created + delivery email sent

**Main Modules**:
- Landing page engine: static HTML files per lead magnet, with a shared CSS design system
- Email delivery API: a single serverless function (`/api/subscribe`) that handles all lead magnets by slug
- Manifest JSON: a single file that drives the public resource directory
- Email copy JSON: a single file that stores all delivery email subjects and bodies indexed by slug
- Notion pipeline DB: tracks status, content URL, slug, and metadata per lead magnet
- Slash commands: Claude Code custom commands that execute the full pipeline

**Key Screens/Pages**:
- `/lead-magnets/[slug].html` — individual landing page per resource
- `/recursos.html` (or equivalent) — public resource directory with search and filters
- Notion pipeline database — operator's review and approval interface
- Notion content pages — the actual lead magnet content subscribers receive

**Main User Actions**:
- Operator: run a slash command, review content in Notion, change a status field
- Subscriber: read landing page, fill and submit form, receive email, read Notion content

**Current Limitations**:
- Single language per landing page (no automatic language switching)
- No A/B testing
- No post-delivery nurture sequences (handled externally by the ESP)
- No dashboard or analytics beyond Brevo's native reporting
- Notion is required as the content delivery surface (PDF delivery requires additional scripting)

---

## 5. Value Proposition

**Primary Benefit**: A new lead magnet goes from concept to live in 15 minutes of active work, with full brand consistency enforced automatically.

**Secondary Benefits**:
- Every landing page follows the same visual system — no design decisions per resource
- Copy voice rules are enforced at the command level — no editing required for consistency
- Email delivery is zero-configuration after the first setup
- The public directory updates automatically on every push

**Differentiators**:
- The system enforces brand rules at the AI prompt level, not at a UI level — rules cannot be skipped
- Two-command workflow covers the entire funnel from research to live page
- Architecture is designed for white-labeling: one configuration layer, no brand references in code

**Current Alternatives** (without this system):
- Manual HTML + email setup per lead magnet (~6h each)
- Page builders (Webflow, Framer) — require manual setup, no content automation
- Email marketing platforms' landing page builders — limited design control, vendor lock-in

---

## 6. Scope

### 6.1 In Scope Today
- HTML landing pages per lead magnet with enforced design system
- Single serverless subscribe endpoint routing by slug
- Brevo contact creation and transactional email delivery
- Notion as pipeline DB and content delivery surface
- Manifest JSON driving a live public resource directory with search + filters
- Email copy JSON as the single source of truth for delivery emails
- Reverse proxy routing from main domain to engine
- Claude Code slash commands: `/lead-magnet` and `/execute-lead-magnets`
- Dark/light theme with CSS custom properties
- Segmentation field (select dropdown) on every form
- Automated git commit and push as the final deploy step

### 6.2 Out of Scope Today
- Payment or checkout pages
- Gated content beyond email capture
- A/B testing
- Post-delivery email sequences
- Multi-language landing pages
- PDF generation (requires additional scripting — out of default scope)
- Analytics dashboard
- User authentication for subscribers
- CMS admin interface (Notion is the operator UI)

### 6.3 Future Scope
- PDF lead magnet format with automated generation
- Lead magnet performance dashboard
- SSO or passwordless subscriber portal
- Automated A/B test for headline variants

---

## 7. Functional Requirements

### FR-01 — Lead Magnet Content Generation
**Description**: The `/lead-magnet [topic]` command researches the topic via web search, writes the full lead magnet content in the operator's brand voice, creates a formatted Notion page with the content, and registers a new row in the Notion pipeline database.  
**Current Status**: Implemented  
**User Story**: As an operator, I want to type one command and get a complete draft lead magnet ready for review, so I can focus on editing rather than writing from scratch.  
**Acceptance Criteria**:
- Web search is performed for the topic before writing begins
- Content follows all prohibited-words rules and brand voice guidelines
- A Notion page is created with H1/H2 headings, callout blocks, and numbered lists
- A pipeline DB row is created with: Name, Status="Draft", Slug, Topic, Notion URL
- The command output shows the Notion content page URL and the pipeline row URL  
**Dependencies**: Notion MCP, web search capability  
**Edge Cases**: Topic is ambiguous → Claude should pick the most commercially relevant angle; Notion API fails → surface error, do not create partial records  
**Notes**: The command does NOT generate HTML, email, or deploy anything. Generation and deployment are separated for operator review.

---

### FR-02 — Landing Page Generation
**Description**: The `/execute-lead-magnets` command reads all pipeline items with Status="Execute", generates a static HTML landing page per item at `website/lead-magnets/[slug].html`.  
**Current Status**: Implemented  
**User Story**: As an operator, I want the landing page to be generated automatically from Notion content so I never write HTML.  
**Acceptance Criteria**:
- Page replicates the reference template exactly (CSS variables, layout, sections)
- Hero section includes: eyebrow label, H1, subtitle, benefit bullets, stat bar (if 2+ real numeric data points exist), and segmentation form
- Form calls `POST /api/subscribe` with `{ firstName, email, slug, field }` — never a third-party endpoint directly
- Nav logo links to the main brand domain homepage
- Footer includes a link to the public resource directory
- Body includes diagonal-stripe background pattern (dark and light theme variants)
- Stat bar includes exactly 3 cells with pattern: pain → pain → benefit
- Form card title names the specific deliverable (not a generic "Get the free resource")
- Page passes W3C HTML validation  
**Dependencies**: Reference template file, Notion content page for the item  
**Edge Cases**: Slug already exists → overwrite; missing stat data → omit stat bar entirely; Notion page unreachable → abort with error

---

### FR-03 — Delivery Email Registration
**Description**: During `/execute-lead-magnets`, a delivery email entry is written to `website/lead-magnet-emails.json` for each processed slug.  
**Current Status**: Implemented  
**User Story**: As an operator, I want the delivery email to be pre-written and automatically wired up so subscribers receive their resource immediately after subscribing.  
**Acceptance Criteria**:
- Entry added under the slug key with `subject`, `tag`, and `body` fields
- Body uses `{{first_name}}` as the variable for subscriber name
- Existing entries are never overwritten — only new slugs are appended
- Subject line is ≤8 words with no exclamation marks  
**Dependencies**: `website/lead-magnet-emails.json` must exist before execution  
**Edge Cases**: Slug already exists in JSON → skip, log warning, do not overwrite

---

### FR-04 — Subscribe API
**Description**: A single serverless function at `POST /api/subscribe` handles all lead magnet form submissions by routing on the `slug` parameter.  
**Current Status**: Implemented  
**User Story**: As a subscriber, I want to submit my email and receive my resource within minutes, with no manual action from the operator.  
**Acceptance Criteria**:
- Accepts `{ firstName, email, slug, field }` in the request body
- Creates or updates a contact in Brevo with `firstName`, `email`, and the segmentation `field` value
- Adds the contact to the configured Brevo list
- Sends a transactional email using the subject and body from `lead-magnet-emails.json` for the given slug
- Returns `200 { success: true }` on success
- Returns `400` if `email` or `slug` is missing
- Returns `500` with error message if Brevo API fails
- Sender name and email are hardcoded in the function — NOT environment variables — to prevent silent production failures  
**Dependencies**: `BREVO_API_KEY` and `BREVO_LIST_ID` environment variables set in Vercel  
**Edge Cases**: Duplicate email → Brevo upserts, no error; unknown slug → sends no email, logs warning; Brevo API timeout → return 500, log error  
**Security**: All inputs must be sanitized before being sent to Brevo. No user input is ever executed or rendered as HTML server-side.

---

### FR-05 — Public Resource Directory
**Description**: A public HTML page fetches `lead-magnets.json` at runtime and renders all published resources with topic badges, difficulty level badges, a search bar, and topic/level filter pills.  
**Current Status**: Implemented  
**User Story**: As a subscriber, I want to browse all available resources in one place and filter by topic or difficulty so I can find what's most relevant to me.  
**Acceptance Criteria**:
- Fetches manifest from the engine repo's `lead-magnets.json` via HTTPS
- Renders each resource with: title, description, topic tags, level badge, and CTA link
- Search filters by title, description, and tags in real time
- Topic filter pills show only topics present in the current manifest
- Level filter buttons: Beginner, Intermediate, Advanced
- Empty state is shown when no results match
- Counter shows "X resources" updating on filter changes
- Works without JavaScript disabled only for the static fallback (progressive enhancement encouraged but not required)  
**Dependencies**: `lead-magnets.json` must be publicly accessible via the engine domain  
**Edge Cases**: Manifest fetch fails → show error state, not blank page; resource has no `tags` → renders without badges, not excluded

---

### FR-06 — Live Manifest
**Description**: `website/lead-magnets.json` is the single source of truth for all published resources. It is read by the public resource directory at runtime.  
**Current Status**: Implemented  
**User Story**: As an operator, I want adding a new lead magnet to the manifest to automatically make it appear in the public directory, with no changes to the main site.  
**Acceptance Criteria**:
- Each entry includes: `slug`, `tags` (array), `level` (string), and at minimum one locale key with `title` and `desc`
- A push to the main branch immediately makes the updated manifest live (no cache invalidation step needed for the directory page)
- The manifest is valid JSON — CI or pre-commit should reject malformed JSON  
**Edge Cases**: Entry added without `tags` or `level` → resource appears but filter doesn't surface it (acceptable degraded behavior)

---

### FR-07 — Reverse Proxy Routing
**Description**: The main brand domain serves lead magnet pages at `[main-domain]/[slug]` by proxying requests to the engine domain, without changing the visible URL.  
**Current Status**: Implemented  
**User Story**: As a subscriber, I want to access resources at the main brand domain so links look clean and trustworthy.  
**Acceptance Criteria**:
- `[main-domain]/[slug]` proxies to `[engine-domain]/lead-magnets/[slug]`
- `[main-domain]/api/subscribe` proxies to `[engine-domain]/api/subscribe` preserving method and body
- All existing pages on the main domain (`/`, `/about`, etc.) continue to work — proxy only fires for unknown paths
- Proxy implemented in `vercel.json` rewrites — no DNS changes required  
**Dependencies**: Both repos deployed on Vercel  
**Edge Cases**: Engine domain is down → proxy returns 502; slug matches an existing page → file takes precedence over proxy (Vercel rewrites are filesystem-first)

---

### FR-08 — Notion Pipeline DB
**Description**: A Notion database tracks every lead magnet through its lifecycle from Draft to Complete.  
**Current Status**: Implemented  
**User Story**: As an operator, I want to review, approve, and trigger production from a single Notion view without touching any files.  
**Acceptance Criteria**:
- DB columns: Name, Status (select), Format (select), Slug (text), Topic (text), Notion URL (URL), Landing URL (URL), Tags (multi-select), Level (select), Published (checkbox)
- Status flow: Draft → Execute → In Progress → Complete
- `/execute-lead-magnets` reads items with Status="Execute", updates to "In Progress" immediately, then "Complete" after successful deploy
- Landing URL is updated to the live page URL on completion  
**Dependencies**: Notion MCP connected to Claude Code  
**Edge Cases**: Item set to "Execute" but Notion page has no content → abort with error, revert to "Draft"

---

### FR-09 — Design System Enforcement
**Description**: All landing pages must share a single visual design system defined by CSS custom properties and enforced by the reference template.  
**Current Status**: Implemented  
**User Story**: As an operator, I want every landing page to look like it belongs to the same brand without manually applying styles.  
**Acceptance Criteria**:
- All pages use the same CSS custom properties (`--color-bg`, `--color-accent`, `--font-display`, etc.)
- Dark/light theme toggle works via `data-theme` attribute on `<html>`
- Body background always includes the diagonal stripe pattern
- Stat bar CSS is present in every page that includes a stat bar
- Form segmentation select uses the full `.form-select` CSS block — no browser-native styling
- Display font (e.g. Anton) loaded from Google Fonts for stat numbers
- Body font (e.g. Inter) loaded from Google Fonts for all text  
**Edge Cases**: Google Fonts unavailable → system font fallback must be defined in `font-family` stack

---

## 8. Non-Functional Requirements

### Performance
- Landing pages are static HTML — target Time to First Byte (TTFB) < 200ms on Vercel Edge
- `/api/subscribe` serverless function must complete within Vercel's 10-second function timeout under normal conditions
- `lead-magnets.json` manifest fetch must complete within 3 seconds; the directory page must render within 5 seconds of the fetch completing
- No client-side JavaScript bundles — all interactivity is vanilla JS inline or in a small script block

### Scalability
- The system is designed for a single operator and low-to-medium subscriber volume (assumption: <50,000 form submissions/month)
- Horizontal scaling is handled by Vercel's serverless infrastructure — no configuration needed
- New lead magnets are additive (new files, new JSON entries) — no schema migrations

### Security
- `BREVO_API_KEY` must be stored as a Vercel environment variable, never committed to the repo
- The subscribe API must validate that `email` and `slug` are present before calling Brevo
- No user-submitted data is ever rendered server-side or stored in the codebase
- `.env` files must never be committed (enforced via `.gitignore`)
- The sender identity is hardcoded in the serverless function — this is a deliberate security and reliability decision to prevent silent misconfiguration via missing env vars

### Availability
- Target: 99.9% monthly uptime (Vercel SLA for Pro plan)
- If the Brevo API is unavailable, the subscribe function returns a 500 error — the subscriber sees an error message and can retry
- Landing pages are static and served independently of the API — a Brevo outage does not take down the landing pages

### Reliability
- If `/execute-lead-magnets` fails mid-run, the operator can re-run it — existing files are overwritten safely and JSON entries are skipped if the slug already exists
- No partial states: if email registration in `lead-magnet-emails.json` fails, the landing page is not deployed

### Compliance
- No cookies are set by the landing pages beyond what the browser requires
- GDPR: the subscribe form collects first name and email only; subscribers must be informed via privacy policy (not part of this system — operator responsibility)
- No analytics scripts embedded in landing pages by default

### Accessibility
- All landing pages must have: `<lang>` attribute on `<html>`, `<alt>` on all images, form labels associated to inputs via `for`/`id`, sufficient color contrast for text (WCAG AA minimum)

### Observability
- Brevo provides per-campaign email delivery reports and per-contact event history
- Vercel provides function logs for the subscribe API
- No custom application-level logging is implemented — Vercel + Brevo logs are the observability layer

### Maintainability
- The reference template (`conversion-landing-sample.html`) is the single source of truth for the visual design — all generated pages must match it
- The `CLAUDE.md` file in the repo root contains all brand and technical rules — it is read at the start of every Claude Code session
- All slash commands are defined as Markdown files in `.claude/commands/` — editing a command file changes the behavior of the corresponding slash command immediately

---

## 9. System Architecture

**Frontend**: Static HTML + vanilla JavaScript + CSS custom properties. No framework. No build step. Pages are served directly by Vercel's CDN.

**Backend**: Single Vercel serverless function (`/api/subscribe.js`) — Node.js. No server, no database, no session management.

**Database**: None. State lives in:
- Git (landing pages, manifest JSON, email JSON)
- Notion (pipeline DB and content pages)
- Brevo (contact list and delivery events)

**Authentication**: None for subscribers. Operator authenticates via git + Vercel CLI + Notion OAuth (for MCP).

**Authorization**: None for subscribers. The subscribe endpoint is public by design (rate limiting is Vercel's default function limits).

**APIs**:
- `POST /api/subscribe` — the only custom API endpoint
- Brevo Contacts API — create/update contacts
- Brevo Transactional Email API (SMTP or API) — send delivery emails
- Notion API — read/write pipeline DB and content pages (via MCP)

**Third-Party Services**:
- Vercel — hosting for both repos, serverless functions, reverse proxy via `vercel.json`
- Brevo — ESP: contact list management + transactional email delivery
- Notion — content CMS and pipeline management
- Google Fonts — typography (Anton for display numbers, Inter for body text)
- GitHub — source control for both repos

**Hosting/Infrastructure**:
- Engine repo: deployed to Vercel project `[engine-project-name]` at `[engine-domain].vercel.app`
- Facade repo: deployed to Vercel project `[facade-project-name]` at the main custom domain
- Both projects are on the same Vercel account
- No Docker, no VPS, no CI/CD pipeline — Vercel auto-deploys on push to `main`

**Background Jobs**: None. Email delivery is synchronous within the serverless function.

**File Storage**: None. Content pages live in Notion. PDFs (if used) would be stored in Notion or a public Vercel URL.

**AI Components**:
- Claude Code (Anthropic CLI) — runs slash commands, writes all HTML/copy/email content
- Model: Claude Opus or Sonnet (configurable) — used for research, writing, and code generation
- No AI inference at runtime — all AI-generated content is committed as static files before deploy

**External Dependencies**:
- `node-fetch` or native `fetch` (Node 18+) for Brevo API calls in the serverless function
- No npm packages beyond what Vercel provides natively

**Two-Repo Architecture**:
```
[main-brand-domain]/              ← Facade repo
  ├── index.html                  (main landing page)
  ├── recursos.html               (resource directory — fetches manifest live)
  ├── vercel.json                 (reverse proxy rewrites → engine)
  └── assets/

[engine-domain].vercel.app/       ← Engine repo (this repo)
  ├── website/
  │   ├── lead-magnets/
  │   │   └── [slug].html         (one file per lead magnet)
  │   ├── lead-magnets.json       (manifest — read by facade at runtime)
  │   ├── lead-magnet-emails.json (email copy index)
  │   └── api/
  │       └── subscribe.js        (serverless function)
  ├── lead-magnet-system/
  │   ├── reference/
  │   │   └── conversion-landing-sample.html  (design system reference)
  │   └── scripts/                (PDF generation scripts, if used)
  └── .claude/
      └── commands/
          ├── lead-magnet.md
          └── execute-lead-magnets.md
```

---

## 10. Data Model Overview

**Entities**:

| Entity | Where stored | Key fields |
|---|---|---|
| Lead Magnet | `lead-magnets.json` + Notion DB | slug, title, desc, tags[], level, landing URL |
| Delivery Email | `lead-magnet-emails.json` | slug (key), subject, tag, body |
| Subscriber | Brevo contact list | email, firstName, segmentation field, list ID |
| Pipeline Item | Notion DB | Name, Status, Format, Slug, Topic, Notion URL, Landing URL, Tags, Level, Published |
| Landing Page | `website/lead-magnets/[slug].html` | Static HTML file — no structured fields |

**Relationships**:
- One Lead Magnet → One Landing Page (1:1, by slug)
- One Lead Magnet → One Delivery Email entry (1:1, by slug)
- One Lead Magnet → One Pipeline Item (1:1, by slug)
- One Subscriber → Many Lead Magnets (via Brevo tags — one tag per lead magnet subscribed to)

**Sensitive Data**:
- `BREVO_API_KEY` — must be stored in Vercel environment variables only, never in code or committed files
- Subscriber email addresses — stored in Brevo only, never logged or committed to the repo

**Data Retention**:
- Landing pages: permanent (deleted manually if the lead magnet is retired)
- Email entries in JSON: permanent (historical record of all delivery emails)
- Subscriber data: governed by Brevo's data retention and operator's privacy policy

**Privacy Considerations**:
- The subscribe API does not log email addresses to any file or console
- No subscriber data touches the git repository at any point
- The operator must configure their own privacy policy and unsubscribe flow in Brevo

---

## 11. User Flows

### Flow 1 — Operator Creates a New Lead Magnet

**Trigger**: Operator runs `/lead-magnet [topic]` in Claude Code  
**Steps**:
1. Claude performs web research on the topic
2. Claude writes the full lead magnet content in brand voice, checking against prohibited words
3. Claude creates a Notion page with the formatted content
4. Claude creates a row in the Notion pipeline DB: Status="Draft", Slug=[generated], Notion URL=[page URL]
5. Claude outputs a summary with the Notion page URL and pipeline row URL

**System Response**: Notion page created, pipeline row created  
**Success State**: Operator sees the content in Notion and can edit it  
**Failure States**:
- Notion MCP unavailable → Claude surfaces error, no partial records created
- Web search fails → Claude proceeds without research data, notes this in the content

---

### Flow 2 — Operator Approves and Deploys

**Trigger**: Operator changes pipeline item Status to "Execute" in Notion, then runs `/execute-lead-magnets`  
**Steps**:
1. Claude reads all pipeline items with Status="Execute"
2. Claude immediately updates each item to Status="In Progress"
3. For each item, Claude reads the Notion content page
4. Claude generates `website/lead-magnets/[slug].html` from the reference template
5. Claude writes the delivery email entry into `lead-magnet-emails.json`
6. Claude updates the pipeline item: Status="Complete", Landing URL=[live URL]
7. Claude runs `git add`, `git commit`, `git push`
8. Vercel auto-deploys on push — landing page is live within ~30 seconds

**System Response**: HTML files committed and pushed, Notion updated, Vercel deploys  
**Success State**: Operator sees "Complete" in Notion, landing page is live, email delivery works  
**Failure States**:
- No items with Status="Execute" → Claude reports this and stops
- Notion content page unreachable → abort that item, revert to Status="Draft"
- Git push fails → Claude surfaces error; Notion statuses remain "In Progress" — operator must push manually or re-run

---

### Flow 3 — Subscriber Opts In

**Trigger**: Subscriber fills and submits the form on a landing page  
**Steps**:
1. Browser sends `POST /api/subscribe` with `{ firstName, email, slug, field }`
2. Serverless function validates required fields
3. Function calls Brevo API to create/update contact with firstName, email, list ID, and segmentation field value
4. Function looks up `lead-magnet-emails.json` by slug, retrieves subject and body
5. Function calls Brevo transactional email API with the pre-written copy, substituting `{{first_name}}`
6. Function returns `200 { success: true }`
7. Landing page shows success state

**System Response**: Contact created in Brevo, delivery email sent  
**Success State**: Subscriber receives email within ~2 minutes with the resource link  
**Failure States**:
- Missing email or slug → 400 error, form shows error message
- Brevo API error → 500 error, form shows "Please try again" message; contact may or may not have been created

---

### Flow 4 — Subscriber Browses Resource Directory

**Trigger**: Subscriber visits `[main-domain]/recursos` (or equivalent)  
**Steps**:
1. Browser loads the static directory HTML page
2. JavaScript fetches `lead-magnets.json` from the engine domain
3. JavaScript renders all resources as cards with badges
4. Subscriber uses search or filter pills to narrow down results

**Success State**: Subscriber sees all resources, clicks through to a landing page  
**Failure States**:
- Manifest fetch fails → error state shown, not a blank page
- No resources match current filters → empty state with message shown

---

## 12. Edge Cases

- **Duplicate slug on generate**: `execute-lead-magnets` overwrites the HTML file. The operator must ensure slugs are unique; there is no automatic slug collision check.
- **Duplicate slug in email JSON**: The command skips writing the entry and logs a warning. It does NOT overwrite existing delivery emails.
- **Missing `tags` or `level` in manifest**: Resource appears in the directory but is not surfaced by topic or level filters. Acceptable degraded behavior.
- **Stat bar with insufficient real data**: Stat bar is omitted entirely rather than using invented numbers. The landing page renders without it.
- **Brevo duplicate subscriber**: Brevo upserts contacts — no error, no duplicate created.
- **Form submitted with empty firstName**: `firstName` is optional in the Brevo contact schema. The email is still sent; `{{first_name}}` renders as empty or the default Brevo fallback.
- **Notion page deleted before execute**: The command finds the pipeline item with Status="Execute" but cannot read the content page. It aborts that item, reverts Status to "Draft", and continues with other items.
- **Git push conflict**: The operator must resolve manually. Claude does not force-push.
- **Vercel deploy takes >60 seconds**: The landing page is not immediately live, but the git commit is already made. The operator can check Vercel dashboard for deploy status.
- **Google Fonts CDN unavailable**: Pages fall back to the system font stack defined in `font-family`. Layout may shift slightly.
- **Manifest JSON malformed**: The resource directory shows an error state. Claude must validate JSON before committing — the pre-commit hook or deploy step should reject malformed JSON.
- **Lead magnet slug contains special characters**: Slugs must be lowercase, hyphen-separated, ASCII only. The command enforces this on generation, but the operator must not manually enter non-compliant slugs in Notion.

---

## 13. Known Issues and Technical Debt

- **No pre-commit JSON validation**: `lead-magnets.json` and `lead-magnet-emails.json` can be committed with malformed JSON if the operator edits them manually. A pre-commit hook (e.g. `jq .` check) would prevent this.
- **Sender hardcoded in subscribe.js**: The email sender name and address are hardcoded strings in the function, not environment variables. This is intentional (see Decision Log) but makes rebranding require a code edit.
- **No retry logic in subscribe.js**: If Brevo's transactional email API times out, the contact is created but no email is sent. The subscriber would need to re-subscribe to trigger the email.
- **No idempotency key on Brevo contact creation**: Concurrent form submissions for the same email could create a race condition on the Brevo contact upsert. Brevo's API handles this gracefully in practice, but it is not explicitly tested.
- **No automated tests**: There are no unit or integration tests for the subscribe function or the landing page generation. Manual testing is the only validation.
- **Landing pages are not validated post-generation**: Claude generates HTML but does not run a validator or check for visual regressions. A screenshot-based smoke test would improve confidence.
- **Notion pipeline has no required field validation**: An operator can set Status="Execute" on an item with no Notion URL, causing the execute command to fail. The command should validate required fields before starting.
- **No rate limiting on the subscribe endpoint**: The function is exposed publicly with no rate limiting beyond Vercel's default limits. A bad actor could spam Brevo with fake contacts. Adding a CAPTCHA or a simple honeypot field would mitigate this.
- **`tutorial-claude-design` page exists but may not be in the manifest**: It is possible for an HTML file to exist without a corresponding manifest entry, making the resource unreachable from the directory. Audit should verify manifest completeness matches the HTML file inventory.

---

## 14. Risks and Mitigations

| Risk | Impact | Mitigation |
|---|---|---|
| Brevo changes its API schema | Delivery emails fail silently | Pin the Brevo API version in the subscribe function; add integration test |
| Vercel changes how rewrites work | Reverse proxy breaks — landing pages serve from engine domain instead of main domain | Monitor Vercel changelog; the engine domain URLs remain functional as fallback |
| Operator accidentally commits `.env` | API key exposed in public repo | `.gitignore` enforces this; GitHub secret scanning provides a second layer |
| Notion MCP loses authentication | `/lead-magnet` and `/execute-lead-magnets` fail | Re-authenticate Notion MCP; no production impact since landing pages are static |
| Landing page HTML drifts from reference template | Visual inconsistency across pages | The reference template is the source of truth; re-run execute for affected pages |
| Brevo free plan limits hit | Email delivery stops | Monitor Brevo usage; upgrade plan before hitting limits |
| Operator publishes a lead magnet with no email entry | Subscribers submit form but receive no email | Add a pre-deploy check that verifies every slug in `lead-magnets.json` has an entry in `lead-magnet-emails.json` |
| Git history exposes old API keys | Security breach if keys were ever committed | Rotate keys immediately; use `git filter-repo` to purge history |

---

## 15. Assumptions

- The operator has a Vercel account with both repos deployed under the same account
- The operator has a Brevo account with at least one contact list configured
- The operator has a Notion workspace with the pipeline database created
- The operator uses Claude Code (Anthropic's CLI) with Notion MCP connected
- Subscribers have access to email and can follow a link to Notion
- Notion pages used as content delivery surfaces are set to "Anyone with link can view" or equivalent public access
- The main brand domain is a custom domain attached to a Vercel project (not a `.vercel.app` subdomain)
- The operator's brand has a defined copy voice documented in `CLAUDE.md` before running any commands
- The operator is the only person running commands against the system (no multi-operator concurrency)
- Internet access is available on the operator's machine when running Claude Code commands

---

## 16. Decision Log

| Decision | Reason | Alternatives Considered | Date |
|---|---|---|---|
| Sender hardcoded in subscribe.js, not env vars | `SENDER_NAME`/`SENDER_EMAIL` env vars were not configured in Vercel. Using `process.env.X` for unconfigured vars silently sends emails from `undefined`, corrupting the sender identity with no error thrown | Store sender in env vars | At initial implementation |
| Static HTML, no framework | No build step = instant deploy; pages load fast; no dependencies to maintain; Claude can generate valid HTML without a framework | Next.js, Astro, Webflow | At initial implementation |
| Single `/api/subscribe` endpoint for all slugs | One function to maintain; routing by slug is trivial; adding a new lead magnet requires zero API changes | One function per lead magnet | At initial implementation |
| Two-repo architecture | Separates the engine (changes frequently with new lead magnets) from the facade (main brand site, changes rarely); operators can use their existing brand repo as the facade | Single repo with subdirectory routing | At initial implementation |
| JSON files as database for manifest and emails | No database to provision or maintain; files are version-controlled with full history; instant reads with no latency | SQLite, Supabase, PlanetScale | At initial implementation |
| Notion as content delivery surface | Operators already use Notion; no need to build a CMS; Notion handles formatting, mobile layout, and sharing | Custom PDF, Webflow page, Google Docs | At initial implementation |
| Reverse proxy via Vercel rewrites | Both repos are on Vercel — no DNS changes, no extra infrastructure, zero latency overhead compared to an external proxy | CNAME flattening, Cloudflare Worker, separate domain per lead magnet | After initial implementation |
| Diagonal stripe background pattern with very low opacity | Adds visual texture without distracting from content; works in both dark and light themes; a single CSS block applied globally | Solid background, noise texture, gradient mesh | Design system decision |
| Stat bar omitted if fewer than 2 real numeric data points exist | Invented or irrelevant statistics erode trust; a clean hero without stats is better than a hero with weak stats | Always include 3 stats, use projections if needed | Design system decision |

---

## 17. Success Metrics

### Business Metrics
- New email subscribers added per lead magnet per month
- Cost per subscriber (operator time cost ÷ new subscribers)
- Reduction in per-lead-magnet production time (baseline: ~6h; target: ≤15min active work)

### Product Metrics
- Form conversion rate per landing page (form submissions ÷ unique page visits)
- Email open rate for delivery emails
- Email click-through rate on the resource link
- Number of lead magnets published per month (cadence)
- Time from `/execute-lead-magnets` to live page (should be < 2 minutes)

### Technical Metrics
- Subscribe API error rate (target: < 1% of submissions)
- Subscribe API p95 response time (target: < 5 seconds)
- Vercel function invocation failures per day
- Manifest fetch success rate from the resource directory (target: 100%)
- Zero broken links in the resource directory

---

## 18. Roadmap

### Phase 1 — Stabilization
- Add pre-commit JSON validation for `lead-magnets.json` and `lead-magnet-emails.json`
- Add pre-deploy check: every slug in manifest has a corresponding email entry
- Add honeypot field to subscribe form to reduce spam submissions
- Add retry logic for transactional email send in subscribe.js
- Document all environment variables in `.env.example` with accurate descriptions
- Verify all 11 landing pages match the reference template (visual audit)

### Phase 2 — Improvement
- Add PDF format support with automated generation (headless Chromium via `puppeteer` on Vercel)
- Add basic analytics integration (e.g. Plausible or Fathom) to landing pages
- Add a pre-deploy screenshot smoke test for generated landing pages
- Automate manifest tag/level completeness: every entry must have `tags` and `level` before deploy
- Create a "clone kit" README for operators who want to fork the system for their own brand

### Phase 3 — Scaling
- Multi-operator support: isolate brand configs per operator in separate config files
- A/B headline testing with a simple URL-based variant system
- Subscriber portal (passwordless, magic link) where subscribers can access all resources they've opted into
- Automated performance monitoring: alert if subscribe API error rate exceeds 2% in a rolling hour

---

## 19. Open Questions

- **What is the acceptable email delivery latency?** Currently depends on Brevo's API response time (~1-3 seconds). Is a 10-second delivery window acceptable to subscribers?
- **Should `lead-magnet-emails.json` be editable from Notion?** Today it's a git-committed JSON file. Would a Notion-driven email copy store improve operator experience for future edits?
- **Is there a max number of lead magnets the resource directory can handle before pagination is needed?** The current implementation loads all resources at once. At 50+ resources, filtering UX may degrade without pagination.
- **Should the reverse proxy cover all lead magnet assets?** If a lead magnet ever includes images or downloadable files served from the engine domain, those URLs would expose the engine domain. Currently all pages are self-contained HTML with external font CDN only.
- **What is the strategy for retiring a lead magnet?** Removing it from the manifest removes it from the directory, but the landing page HTML and URL remain live. Should retired pages show a redirect or a "no longer available" state?
- **What happens if the Notion content page is edited after the landing page is already generated?** The landing page is a static snapshot. The operator must re-run `/execute-lead-magnets` for updates to propagate. Is this acceptable, or should there be a "re-generate" command?
- **Should the subscribe API log any analytics events?** Currently there is no application-level event logging. Adding structured logs (e.g. `{ event: "subscribe", slug, timestamp }`) to Vercel's log drain would enable custom analytics.
- **What is the compliance posture for GDPR/CCPA?** The subscribe form collects email and first name. The operator is responsible for privacy policy, consent mechanism, and unsubscribe flow. Should this system include a consent checkbox on the form?

---

## Appendix A — Environment Variables Required

| Variable | Where used | Description |
|---|---|---|
| `BREVO_API_KEY` | `website/api/subscribe.js` | Brevo API key for contact management and transactional email |
| `BREVO_LIST_ID` | `website/api/subscribe.js` | Brevo contact list ID where all subscribers are added |

**Note**: Sender name and email are hardcoded in `subscribe.js`. See Decision Log for rationale.

---

## Appendix B — File Structure Reference

```
[engine-repo]/
├── website/
│   ├── lead-magnets/
│   │   ├── [slug-1].html
│   │   ├── [slug-2].html
│   │   └── ...
│   ├── lead-magnets.json          ← Live manifest (read by directory page)
│   ├── lead-magnet-emails.json    ← Email copy index (read by subscribe API)
│   └── api/
│       └── subscribe.js           ← Only serverless function
├── lead-magnet-system/
│   └── reference/
│       └── conversion-landing-sample.html  ← Design system reference
├── .claude/
│   └── commands/
│       ├── lead-magnet.md         ← Slash command definition
│       └── execute-lead-magnets.md
├── CLAUDE.md                      ← All brand + technical rules (read by Claude Code)
├── .env.example                   ← Documents required env vars
├── vercel.json                    ← (if engine needs Vercel config)
└── package.json                   ← Minimal: only needed if Node scripts are used

[facade-repo]/
├── index.html                     ← Main brand landing page
├── recursos.html                  ← Public resource directory
├── vercel.json                    ← Reverse proxy rewrites → engine
└── assets/
```

---

## Appendix C — lead-magnets.json Schema

```json
[
  {
    "slug": "your-lead-magnet-slug",
    "tags": ["tag1", "tag2"],
    "level": "beginner",
    "es": {
      "title": "Title in primary language",
      "desc": "One-sentence description in primary language."
    },
    "en": {
      "title": "Title in English",
      "desc": "One-sentence description in English."
    }
  }
]
```

Valid values for `level`: `beginner`, `intermediate`, `advanced`  
Valid values for `tags`: any lowercase string; define a closed vocabulary in `CLAUDE.md` for consistency

---

## Appendix D — lead-magnet-emails.json Schema

```json
{
  "your-lead-magnet-slug": {
    "subject": "Short email subject line (max 8 words)",
    "tag": "lead-magnet-your-lead-magnet-slug",
    "body": "Hello {{first_name}},\n\nBody of the delivery email.\n\n[Resource link]\n\n— Your Name"
  }
}
```

**Note**: `{{first_name}}` is the only supported variable. It is replaced by Brevo at send time.

---

## Appendix E — Mandatory Landing Page Rules (Enforced by CLAUDE.md)

Any AI agent generating landing pages for this system must enforce these rules without exception:

1. **Nav logo** must link to the main brand domain homepage — never `href="#"`
2. **Author byline** format: `By [Name] — [Role]` — no geographic modifiers
3. **Segmentation field**: every form must include a `<select>` dropdown between the email field and the submit button, with a question specific to the lead magnet topic
4. **Background pattern**: body must include the diagonal stripe CSS (dark + light variants)
5. **Stat bar**: include only if 2+ real numeric data points exist; never invent statistics; pattern is pain → pain → benefit
6. **Form card title**: must name the specific deliverable (e.g. "Get the 5 prompts free") — never generic
7. **Footer resource link**: must point to the public resource directory — opens in same tab
8. **Prohibited words**: a defined list of marketing buzzwords and filler words must never appear in any copy
9. **Sender in subscribe.js**: hardcoded — never change to environment variables

---

*This document describes the system as built. It is intended for operators cloning this system for their own brand, developers maintaining or extending it, and AI coding agents generating new lead magnets within it.*
