Conversation
Adds a new automation kit that generates hyper-personalized cold outreach emails for college students applying to engineering internships. Takes LinkedIn/profile data and student context as inputs and produces a subject line, email body, and personalization hook via a Gemini-powered Lamatic flow. Made-with: Cursor
Deleted 53 unused shadcn/ui components, theme-provider, and the hooks folder that were copied from the base template but never referenced. Keeps only the 5 components actually used: button, card, input, label, textarea. Made-with: Cursor
📝 WalkthroughWalkthroughAdds a new "Cold Email Personalization" automation kit: a Next.js app, UI components, server-side orchestration, Lamatic workflow configs, a Lamatic client, result parser, and build/config files to run a Lamatic-powered cold-email generation flow. Changes
Sequence DiagramsequenceDiagram
participant Browser as Browser
participant Page as ColdEmailPage<br/>(Client)
participant ServerAction as personalizeColdEmail<br/>(Server)
participant LamaticClient as LamaticClient<br/>(lib)
participant LamaticAPI as Lamatic API
participant Parser as parseColdEmailResult<br/>(lib)
Browser->>Page: User fills form & submits
Page->>Page: Validate inputs & enforce limits
Page->>ServerAction: POST form -> personalizeColdEmail
ServerAction->>ServerAction: Validate config & workflowId
ServerAction->>LamaticClient: executeFlow(workflowId, inputs)
LamaticClient->>LamaticAPI: GraphQL request (uses LAMATIC_API_ envs)
LamaticAPI-->>LamaticClient: Response (result / error)
LamaticClient-->>ServerAction: Raw response
ServerAction->>Parser: parseColdEmailResult(raw result)
Parser-->>ServerAction: {subject_line, email_body, personalized_hook}
ServerAction-->>Page: { success: true, data } or { success: false, error }
Page->>Browser: Render generated output or show error
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (9)
kits/automation/cold-email-personalization/.npmrc (1)
1-1: Document whylegacy-peer-depsis required.This setting bypasses peer dependency checks, which can mask genuine incompatibilities. Consider adding a comment or documenting which specific package conflicts necessitate this workaround (likely React 19 compatibility with older Radix/form libraries).
kits/automation/cold-email-personalization/package.json (1)
16-68: Remove 25 unused Radix UI dependencies to reduce bundle size.The kit declares 27 Radix UI packages but only imports 2 directly (
@radix-ui/react-labeland@radix-ui/react-slot). The remaining 25 packages—accordion, alert-dialog, aspect-ratio, avatar, checkbox, collapsible, context-menu, dialog, dropdown-menu, hover-card, menubar, navigation-menu, popover, progress, radio-group, scroll-area, select, separator, slider, switch, tabs, toast, toggle, tooltip—are unused. Removing them reduces bundle bloat and maintenance surface.kits/automation/cold-email-personalization/lib/lamatic-client.ts (1)
16-20: Remove empty-string fallbacks after strict env checks.Line 17–19 can hide mapping mistakes by silently creating a client with empty credentials. Since Line 4–14 already fail fast, pass validated values directly.
♻️ Proposed fail-fast refactor
export const lamaticClient = new Lamatic({ - endpoint: config.api.endpoint ?? "", - projectId: config.api.projectId ?? "", - apiKey: config.api.apiKey ?? "", + endpoint: config.api.endpoint!, + projectId: config.api.projectId!, + apiKey: config.api.apiKey!, })kits/automation/cold-email-personalization/components/header.tsx (1)
1-47: Rename component file to PascalCase (Header.tsx).This component file is in
components/but uses lowercase filename (header.tsx), which conflicts with the repository component naming rule.As per coding guidelines, "Use PascalCase for React component filenames in the
components/directory".kits/automation/cold-email-personalization/components/ui/input.tsx (1)
1-21: Renameinput.tsxtoInput.tsxfor guideline compliance.This React component file lives under
components/and should use PascalCase naming.As per coding guidelines, "Use PascalCase for React component filenames in the
components/directory".kits/automation/cold-email-personalization/components/ui/label.tsx (1)
1-1: Rename file to PascalCase (Label.tsx).
label.tsxviolates the component filename convention for this directory.As per coding guidelines, "Use PascalCase for React component filenames in the
components/directory".kits/automation/cold-email-personalization/components/ui/textarea.tsx (1)
1-1: Rename file to PascalCase (Textarea.tsx).Current filename
textarea.tsxis not aligned with the components naming convention.As per coding guidelines, "Use PascalCase for React component filenames in the
components/directory".kits/automation/cold-email-personalization/components/ui/button.tsx (2)
1-1: Rename file to PascalCase (Button.tsx).
button.tsxdoes not follow the required filename convention for React components.As per coding guidelines, "Use PascalCase for React component filenames in the
components/directory".
39-56: Default native buttons totype="button"to avoid accidental submits.Line 52 renders a native
<button>path without a default type, so inside forms it will submit unless callers always passtype.Proposed fix
function Button({ className, variant, size, asChild = false, + type, ...props }: React.ComponentProps<'button'> & VariantProps<typeof buttonVariants> & { asChild?: boolean }) { const Comp = asChild ? Slot : 'button' return ( <Comp data-slot="button" + type={asChild ? type : (type ?? 'button')} className={cn(buttonVariants({ variant, size, className }))} {...props} /> ) }
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d290c7ab-841b-4918-ab67-7609dee882f6
⛔ Files ignored due to path filters (2)
.DS_Storeis excluded by!**/.DS_Storekits/automation/cold-email-personalization/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (28)
kits/automation/cold-email-personalization/.env.examplekits/automation/cold-email-personalization/.gitignorekits/automation/cold-email-personalization/.npmrckits/automation/cold-email-personalization/README.mdkits/automation/cold-email-personalization/actions/orchestrate.tskits/automation/cold-email-personalization/app/globals.csskits/automation/cold-email-personalization/app/layout.tsxkits/automation/cold-email-personalization/app/page.tsxkits/automation/cold-email-personalization/components.jsonkits/automation/cold-email-personalization/components/header.tsxkits/automation/cold-email-personalization/components/ui/button.tsxkits/automation/cold-email-personalization/components/ui/card.tsxkits/automation/cold-email-personalization/components/ui/input.tsxkits/automation/cold-email-personalization/components/ui/label.tsxkits/automation/cold-email-personalization/components/ui/textarea.tsxkits/automation/cold-email-personalization/config.jsonkits/automation/cold-email-personalization/flows/cold-email-personalisation/README.mdkits/automation/cold-email-personalization/flows/cold-email-personalisation/config.jsonkits/automation/cold-email-personalization/flows/cold-email-personalisation/inputs.jsonkits/automation/cold-email-personalization/flows/cold-email-personalisation/meta.jsonkits/automation/cold-email-personalization/lib/lamatic-client.tskits/automation/cold-email-personalization/lib/parse-cold-email-result.tskits/automation/cold-email-personalization/lib/utils.tskits/automation/cold-email-personalization/next.config.mjskits/automation/cold-email-personalization/orchestrate.jskits/automation/cold-email-personalization/package.jsonkits/automation/cold-email-personalization/postcss.config.mjskits/automation/cold-email-personalization/tsconfig.json
kits/automation/cold-email-personalization/actions/orchestrate.ts
Outdated
Show resolved
Hide resolved
kits/automation/cold-email-personalization/actions/orchestrate.ts
Outdated
Show resolved
Hide resolved
kits/automation/cold-email-personalization/flows/cold-email-personalisation/README.md
Show resolved
Hide resolved
kits/automation/cold-email-personalization/lib/parse-cold-email-result.ts
Outdated
Show resolved
Hide resolved
- Add .env.local and .env*.local to .gitignore to prevent secret commits - Remove quotes from .env.example values (dotenv-linter compliance) - Fix layout.tsx metadata (remove v0 placeholders) - Refactor lamatic-client to lazy getter so init errors are caught by the action's try/catch - Add server-side input validation in orchestrate.ts (mirrors client-side guards) - Tighten parser to reject partial outputs with empty subject_line or email_body - Pin lamatic to 0.3.2 and react-markdown to 10.1.0 (was "latest") - Remove ~40 unused radix/shadcn dependencies from package.json Made-with: Cursor
There was a problem hiding this comment.
♻️ Duplicate comments (1)
kits/automation/cold-email-personalization/lib/parse-cold-email-result.ts (1)
19-63:⚠️ Potential issue | 🟠 MajorFail fast on partial outputs and nested schema echoes.
ColdEmailOutputand the page state both treatpersonalized_hookas required, but this still coerces it to"". Also,isSchemaEchoNotEmailData()only becomes actionable for the top-level object, so string or nested schema echoes still degrade to a generic parser failure even thoughpersonalizeColdEmail()already catches parser errors cleanly.💡 Tighten the parser contract and preserve the Lamatic config error
function parseFromJsonString(raw: string): ColdEmailOutput { const cleaned = stripJsonFences(raw) - const parsed = JSON.parse(cleaned) as Record<string, unknown> + const parsed = JSON.parse(cleaned) + if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { + throw new Error("Workflow returned JSON that is not an object.") + } + + const record = parsed as Record<string, unknown> + if (isSchemaEchoNotEmailData(record)) throw new Error(LAMATIC_SCHEMA_ECHO_MESSAGE) - const subject_line = parsed.subject_line - const email_body = parsed.email_body - const personalized_hook = parsed.personalized_hook + const subject_line = record.subject_line + const email_body = record.email_body + const personalized_hook = record.personalized_hook if ( typeof subject_line !== "string" || subject_line.trim() === "" || - typeof email_body !== "string" || email_body.trim() === "" + typeof email_body !== "string" || email_body.trim() === "" || + typeof personalized_hook !== "string" || personalized_hook.trim() === "" ) { - throw new Error("Parsed JSON is missing required subject_line or email_body.") + throw new Error("Parsed JSON is missing required subject_line, email_body, or personalized_hook.") } return { subject_line, email_body, - personalized_hook: typeof personalized_hook === "string" ? personalized_hook : "", + personalized_hook, } } function isColdEmailShape(o: Record<string, unknown>): boolean { return ( typeof o.subject_line === "string" && o.subject_line.trim() !== "" && - typeof o.email_body === "string" && o.email_body.trim() !== "" + typeof o.email_body === "string" && o.email_body.trim() !== "" && + typeof o.personalized_hook === "string" && o.personalized_hook.trim() !== "" ) } function fromShape(o: Record<string, unknown>): ColdEmailOutput { return { subject_line: o.subject_line as string, email_body: o.email_body as string, - personalized_hook: typeof o.personalized_hook === "string" ? o.personalized_hook : "", + personalized_hook: o.personalized_hook as string, } } function tryParseEmailJsonString(s: string): ColdEmailOutput | null { const t = s.trim() if (t.length < 15 || !t.includes("subject_line")) return null try { return parseFromJsonString(t) - } catch { + } catch (error) { + if (error instanceof Error && error.message === LAMATIC_SCHEMA_ECHO_MESSAGE) throw error return null } } function findEmailObject(obj: unknown, depth = 0): ColdEmailOutput | null { @@ const r = obj as Record<string, unknown> + if (isSchemaEchoNotEmailData(r)) { + throw new Error(LAMATIC_SCHEMA_ECHO_MESSAGE) + } if (isColdEmailShape(r)) return fromShape(r)Also applies to: 85-87, 122-165
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b51a404a-1e2c-49b0-adcd-0993bd7dd8dc
📒 Files selected for processing (7)
kits/automation/cold-email-personalization/.env.examplekits/automation/cold-email-personalization/.gitignorekits/automation/cold-email-personalization/actions/orchestrate.tskits/automation/cold-email-personalization/app/layout.tsxkits/automation/cold-email-personalization/lib/lamatic-client.tskits/automation/cold-email-personalization/lib/parse-cold-email-result.tskits/automation/cold-email-personalization/package.json
✅ Files skipped from review due to trivial changes (4)
- kits/automation/cold-email-personalization/.env.example
- kits/automation/cold-email-personalization/.gitignore
- kits/automation/cold-email-personalization/package.json
- kits/automation/cold-email-personalization/app/layout.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- kits/automation/cold-email-personalization/lib/lamatic-client.ts
- kits/automation/cold-email-personalization/actions/orchestrate.ts
Mission Possible
Added Cold Email Personalization Agent Kit — a Next.js automation kit that generates hyper-personalized cold outreach emails for college students applying to engineering opportunities. Users paste a LinkedIn-style profile and their own context, and the agent produces a tailored subject line, email body, and personalization hook using a LLM API via Lamatic flow.