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
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
@10done Could you add a live demo link to this file? would allow us to view the functionality better, and allow users to see the kit before forking it. otherwise, LGTM. Thanks!
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.