Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions kits/outreach-personalizer/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Flow-level API Keys (Required in Lamatic Studio / deployments)
FIRECRAWL_API_KEY=your_firecrawl_api_key
GROQ_API_KEY=your_groq_api_key
5 changes: 5 additions & 0 deletions kits/outreach-personalizer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.lamatic/
node_modules/
.next/
.env
.env.local
86 changes: 86 additions & 0 deletions kits/outreach-personalizer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Outreach Personalizer - Personalized Outreach Generator

Outreach Personalizer is a web application that wraps a deployed Lamatic.ai AI flow. It automatically crawls a prospect company's website and founder's public profile, extracts concrete technical signals/milestones, connects them to a candidate's custom background, and drafts a highly personalized, high-specificity cold outreach email.

## Key Features
- **Crawls Website Content**: Leverages Firecrawl integration inside the Lamatic flow to scrape company data.
- **Specific Signal Hook Extraction**: Uses an LLM to identify specific achievements, posts, or bugs rather than generic praises.
- **Value-Added Asset Suggestion**: Suggests a concrete asset the pitch candidate could build in under 2 hours to showcase capabilities.
- **Tailored Outreach Generation**: Drafts a clean, direct outreach email (80-120 words) with a specific call to action.
- **Premium Dark UI**: Glassmorphic, modern responsive web dashboard with step-by-step agent feedback and copy support.
- **Safe Server-Side Calls**: Never exposes secret credentials to the client side.
- **Resilient Error Interception**: Catches non-JSON / HTML gateway errors gracefully.

---

## Setup Instructions

### Prerequisites
- Node.js v18.0.0 or higher
- npm or yarn

### 1. Configure Environment Variables
Create a file named `.env.local` in the `apps/web/` directory (or copy it from the workspace root) and define the following variables:
Comment on lines +22 to +23

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Setup guide describes an architecture that doesn't exist — self-destruct these instructions before someone follows them.

This document sends the reader to apps/web/ (lines 23, 45) and describes execution via apps/web/app/api/generate/route.ts calling a lib/lamatic.ts wrapper (lines 60, 73-74, 84) using a GraphQL POST diagram. The actual kit implementation uses:

  • Directory apps/ (not apps/web/)
  • A Server Action generatePersonalizedPitch in apps/actions/orchestrate.ts, not an API route handler
  • The client wrapper at apps/lib/lamatic-client.ts, not lib/lamatic.ts

Following these instructions verbatim (cd apps/web) will fail immediately, and the architecture section misrepresents how the flow is actually invoked.

📝 Proposed fix (representative excerpt)
-Create a file named `.env.local` in the `apps/web/` directory (or copy it from the workspace root) and define the following variables:
+Create a file named `.env.local` in the `apps/` directory (or copy it from the workspace root) and define the following variables:
-Change into the `apps/web` directory and install the required modules:
+Change into the `apps/` directory and install the required modules:
```diff
-The app initiates execution of the Lamatic flow using a Next.js Server Handler at `apps/web/app/api/generate/route.ts`. 
+The app initiates execution of the Lamatic flow using a Next.js Server Action (`generatePersonalizedPitch`) defined in `apps/actions/orchestrate.ts`, which calls the `lamaticClient` in `apps/lib/lamatic-client.ts`.

Also applies to: 45-46, 60-86

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 22-22: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kits/outreach-personalizer/README.md` around lines 22 - 23, The README setup
and architecture instructions reference the wrong app structure and execution
flow, so update them to match the actual kit implementation. Replace mentions of
apps/web with the real apps directory, and describe the invocation path through
the generatePersonalizedPitch Server Action in apps/actions/orchestrate.ts using
the lamaticClient wrapper in apps/lib/lamatic-client.ts instead of an API route
or lib/lamatic.ts. Keep the environment/setup steps aligned with the real
directory layout so readers are not sent to nonexistent paths.


```env
# The secret credential used to authenticate your client requests (do not share or commit)
LAMATIC_API_KEY=your_lamatic_api_key

# The project identifier in the Lamatic Cloud Studio
LAMATIC_PROJECT_ID=your_lamatic_project_id

# The base URL endpoint for your deployed project (e.g. https://<org>-<project>.lamatic.dev)
LAMATIC_API_URL=your_lamatic_api_url

# The UUID indicating which specific flow to trigger (e.g. Outreach Personalizer)
FLOW_ID=your_deployed_flow_id
```

> [!IMPORTANT]
> Never commit `.env.local` to source control. It is already added to `.gitignore` to prevent secret exposure.

### 2. Install Dependencies
Change into the `apps/web` directory and install the required modules:
```bash
cd apps/web
npm install
```

### 3. Run Locally
Start the local development server:
```bash
npm run dev
```
Open [http://localhost:3000](http://localhost:3000) in your browser to view the application.

---

## Architectural Details & Integration

The app initiates execution of the Lamatic flow using a Next.js Server Handler at `apps/web/app/api/generate/route.ts`.

```
┌────────────────────────┐
│ Next.js Client │
└───────────┬────────────┘
│ POST (JSON Inputs)
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ Next.js Server App Router │
│ │
│ ┌────────────────────────┐ ┌──────────────────┐ │
│ │ API Route Handler ├──────────────►│ Lamatic Client │ │
│ │ (api/generate/route) │ Executes │ (lib/lamatic.ts) │ │
│ └────────────────────────┘ Client └─────────┬────────┘ │
└─────────────────────────────────────────────────────┼───────────────────────────────────────┘
│ GraphQL POST Request
┌──────────────────────┐
│ Lamatic.ai Gateway │
└──────────────────────┘
```

1. **Client Trigger**: The client form sends `company_url`, `founder_linkedin_url`, and `candidate_context` to the local Next.js `/api/generate` handler.
2. **GraphQL Wrapping**: The `LamaticClient` wrapper formats the GraphQL mutation request using `FLOW_ID` as the workflow identifier.
3. **Execution**: The request is securely dispatched to the deployed endpoint (`LAMATIC_API_URL`) authorized by `LAMATIC_API_KEY`.
4. **Resilient Parsing**: The response text is scanned for HTML content (e.g. Cloudflare error pages) to ensure that raw parse exceptions are intercepted and displayed to the user as a friendly warning.
60 changes: 60 additions & 0 deletions kits/outreach-personalizer/agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Agent: Outreach Personalizer

## Purpose & Overview
The **Outreach Personalizer** agent is designed to construct hyper-personalized cold outreach pitches for job candidates. Instead of writing generic flattery, it acts as an analytical, eager candidate who does deep research to find concrete, high-specificity hooks. It crawls the target's public content, extracts technical signals, devises a small asset (which can be completed in < 2 hours), and builds a confident, direct cold email pitch.

---

## Workflow Steps

The agent's logic is defined across a multi-step chain in `flows/average-teenager.ts`:

```mermaid
graph TD
Start([API Trigger]) --> Scrape[Firecrawl Crawl]
Scrape --> SignalLLM[LLM Node: Extract Signals]
SignalLLM --> AssetLLM[LLM Node: Devise Asset]
AssetLLM --> PitchLLM[LLM Node: Draft Email]
PitchLLM --> Response([Response Output])
```

1. **Firecrawl Node (`firecrawlNode_692`)**: Scrapes both the company website (`company_url`) and the founder's LinkedIn profile (`founder_linkedin_url`) provided in the trigger inputs.
2. **Signal Extraction (`LLMNode_276`)**: Parses the crawled markdown content to extract 3-5 concrete "noticed things" (e.g. a specific feature release, a bug, a technical post, or a design decision) and rates them with a specificity score from 1 to 5.
3. **Asset Suggestion (`LLMNode_996`)**: Connects the candidate's background with one of the extracted signals and suggests one small, concrete value-add asset (e.g. a mini bug list, a UX walkthrough, a content sample, or a competitor teardown) that the candidate can build quickly.
4. **Outreach Drafting (`LLMNode_696`)**: Selects the signal with the highest specificity score and drafts a direct, short cold outreach email around it.

---

## Agent Persona & Style Constraints

- **Tone**: Conversational, confident, direct, and non-corporate. Sounds like one builder writing to another.
- **Formality**: Low-to-moderate; avoids generic professional platitudes.
- **Length**: Strict constraint of 80 to 120 words.
- **Structure**:
1. Hook based on the highest specificity signal (quotes/paraphrases the exact metric/name/result).
2. Connection to the candidate's background.
3. Description of the specific value-add asset.
4. Ends with exactly: *"Would it make sense to speak for 10 minutes?"*
- **Banned Words & Phrases**:
- *"I'd love to"*
- *"could be really useful"*
- *"I hope this finds you well"*
- *"reach out"*
- *"circle back"*
- *"leverage"*
- Em-dashes (`—` or `--`)

---

## API Inputs & Outputs

### Expected Inputs
The trigger accepts a JSON payload with the following fields:
- `company_url` (string): Target company website.
- `founder_linkedin_url` (string): Founder's LinkedIn profile.
- `candidate_context` (string): Context about the candidate's background, skills, and projects.

### Expected Outputs
The flow execution response contains:
- `status` (string): Execution status (e.g. `success`).
- `result` (object/string): The plain text cold outreach email, along with outputs from preceding nodes (signals and asset ideas) if mapped.
4 changes: 4 additions & 0 deletions kits/outreach-personalizer/apps/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
LAMATIC_API_KEY=lt-xxxxxx
LAMATIC_PROJECT_ID=xxxxxx
LAMATIC_API_URL=https://xxxxxx.lamatic.dev
FLOW_ID=xxxxxx
42 changes: 42 additions & 0 deletions kits/outreach-personalizer/apps/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*
!.env.example

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
5 changes: 5 additions & 0 deletions kits/outreach-personalizer/apps/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
<!-- END:nextjs-agent-rules -->
1 change: 1 addition & 0 deletions kits/outreach-personalizer/apps/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
56 changes: 56 additions & 0 deletions kits/outreach-personalizer/apps/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Outreach Personalizer - Next.js Reference App

This is the reference web application for the **Outreach Personalizer** kit. It provides a modern, responsive web dashboard where job candidates can generate hyper-personalized cold outreach emails based on a target company website URL, founder's LinkedIn URL, and the candidate's background.

The application triggers a deployed Lamatic.ai AI flow via Next.js Server Actions, keeping all API credentials secure on the server side.

## Features

- **Input Form**: Clean fields for the company website, founder LinkedIn profile, and candidate background context.
- **Real-Time Step Progression**: Dynamic feedback showing the current step of the underlying agent flow.
- **Copy-to-Clipboard**: Quick copy functionality for the generated cold outreach pitch.
- **Error Interception**: Graceful handling of network failures or flow timeout errors.

## Prerequisites

- Node.js v18.0.0 or higher
- npm, yarn, or pnpm

## Environment Setup

Create a `.env.local` file in this directory (`apps/`), or copy `.env.example` to `.env.local`:

```bash
cp .env.example .env.local
```

Define the following environment variables inside `.env.local`:

```env
# The secret credential used to authenticate your client requests (do not share or commit)
LAMATIC_API_KEY=your_lamatic_api_key

# The project identifier in the Lamatic Cloud Studio
LAMATIC_PROJECT_ID=your_lamatic_project_id

# The base URL endpoint for your deployed project (e.g. https://<org>-<project>.lamatic.dev)
LAMATIC_API_URL=your_lamatic_api_url

# The UUID indicating which specific flow to trigger (e.g. Outreach Personalizer)
FLOW_ID=your_deployed_flow_id
```

## Running the Application

1. **Install dependencies**:
```bash
npm install
```

2. **Run the development server**:
```bash
npm run dev
```

3. **Access the dashboard**:
Open [http://localhost:3000](http://localhost:3000) in your browser.
89 changes: 89 additions & 0 deletions kits/outreach-personalizer/apps/actions/orchestrate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"use server"

Comment thread
coderabbitai[bot] marked this conversation as resolved.
import { getLamaticClient } from "../lib/lamatic-client";
import kitConfig from "../../lamatic.config";

export interface PitchInput {
company_url: string;
founder_linkedin_url: string;
candidate_context: string;
}

const TIMEOUT_MS = 300000; // 5 minutes (Matching Vercel Hobby maxDuration)

/**
* Wraps a promise with a timeout.
*/
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number, errorMessage: string): Promise<T> {
let timer: NodeJS.Timeout | undefined;
const timeoutPromise = new Promise<never>((_, reject) => {
timer = setTimeout(() => reject(new Error(errorMessage)), timeoutMs);
// Ensure node can exit if the timer is the only thing keeping it alive
if (timer.unref) {
timer.unref();
}
});
try {
return await Promise.race([promise, timeoutPromise]);
} finally {
if (timer) {
clearTimeout(timer);
}
}
}

export async function generatePersonalizedPitch(input: PitchInput): Promise<{
success: boolean;
data?: any;
error?: string;
}> {
try {
console.log("[v0] Executing Average Teenager flow for company URL:", input.company_url);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Debug tags survived the mission, but the sensitive payloads didn't.

candidate_context and the raw SDK dump are gone — good. The [v0] log prefixes and the company_url/error-object logging are cosmetic remnants; not a blocker, low priority.

Also applies to: 61-61

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kits/outreach-personalizer/apps/actions/orchestrate.ts` at line 33, The
remaining debug-style logging in orchestrate() still leaks implementation noise,
so clean up the console.log calls tied to the flow execution and error handling
by removing the “[v0]” prefix and avoiding logging sensitive inputs like
company_url or raw error objects. Update the affected log statements in
orchestrate() to use neutral, non-debug messages and keep only the minimal
context needed for normal operation.


const flowStep = kitConfig.steps.find((step) => step.id === "average-teenager");
if (!flowStep) {
throw new Error("Step 'average-teenager' not defined in lamatic.config.");
}
const envKey = flowStep.envKey || "FLOW_ID";
const flowId = process.env[envKey];
if (!flowId) {
throw new Error(`${envKey} is not set in environment variables.`);
}

const trimmedInput = {
company_url: input.company_url.trim(),
founder_linkedin_url: input.founder_linkedin_url.trim(),
candidate_context: input.candidate_context.trim(),
};

const resData: any = await withTimeout(
getLamaticClient().executeFlow(flowId, trimmedInput),
TIMEOUT_MS,
"The AI provider is taking longer than usual to respond. This usually means their service is overloaded. Please try again in a few minutes."
);

if (!resData || (resData.status && resData.status === "error")) {
throw new Error(resData.message || "Flow execution failed or returned no result.");
}

return {
success: true,
data: resData.result,
};
} catch (error: any) {
console.error("[v0] Generation error:", error);

let errorMessage = "An unexpected error occurred during execution.";
if (error instanceof Error) {
errorMessage = error.message;
if (error.message.includes("fetch failed")) {
errorMessage = "Network error: Unable to connect to the Lamatic service. Please check your internet connection.";
}
}

return {
success: false,
error: errorMessage,
};
}
}
Binary file added kits/outreach-personalizer/apps/app/favicon.ico
Binary file not shown.
26 changes: 26 additions & 0 deletions kits/outreach-personalizer/apps/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@import "tailwindcss";

:root {
--background: #ffffff;
--foreground: #171717;
}

@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}

@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}

body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}
Loading
Loading