Beginner ~3 hours

Build an AI Proposal Generator

Create a professional client proposal automation tool that uses Claude AI to generate compelling, customized proposals. Perfect for consulting firms, agencies, and professional services businesses looking to streamline their sales process.

What We're Building

Professional services firms spend countless hours crafting proposals for potential clients. This AI Proposal Generator automates that process by collecting client requirements through an intake form, using Claude to generate customized proposal content, and producing polished PDF documents ready to send.

By the end of this guide, you'll have a full-stack application with:

Reusable proposal templates stored in Supabase
Dynamic client intake forms
AI-powered content generation with Claude
Section-by-section editing interface
Professional PDF export with React-PDF
Proposal tracking dashboard

Prerequisites

Before starting this guide, make sure you have the following:

  • Node.js 18+ installed on your machine
  • Claude API key from Anthropic (get one at console.anthropic.com)
  • Supabase account for database and authentication
  • Vercel account for deployment (free tier works)
  • Basic knowledge of React and TypeScript

Tech Stack Specification

Here's the technology stack we'll use for this build:

Layer Technology Why This Choice
Frontend Next.js 14, Tailwind CSS Form UI, server components, and rapid styling
Backend Next.js API Routes Serverless processing with same-repo deployment
Database Supabase Store templates, proposals, and client data
AI Claude API Best-in-class content generation quality
PDF React-PDF React-native PDF generation with styling
Hosting Vercel Zero-config deployment for Next.js

AI Agent Workflow

Here's how to leverage AI tools throughout this build to maximize productivity:

Project Scaffolding with Claude Code

Use Claude Code to scaffold the entire project structure with a single prompt. This approach ensures consistent patterns and saves hours of boilerplate setup.

Claude Code Prompt
# Example prompt for Claude Code
Create a Next.js 14 proposal generator app with:
- App router structure
- Supabase client setup with types
- API routes for Claude integration
- Tailwind CSS with professional theme
- TypeScript interfaces for Proposal, Template, Client
- Form components with react-hook-form
- PDF generation setup with @react-pdf/renderer

UI Generation with v0.dev

Generate beautiful form components and dashboard layouts using v0.dev. The generated Tailwind components integrate seamlessly with your Next.js project.

Pro Tip

When prompting v0.dev, specify "professional services" or "consulting firm" aesthetic to get corporate-appropriate styling that clients expect from proposals.

Development with Cursor

Use Cursor's AI features for real-time debugging and code completion. The inline chat is particularly useful for crafting Claude prompts and handling edge cases in PDF generation.

Step-by-Step Build Guide

Phase 1: Proposal Template System

First, we'll set up the database schema and template management. Templates define the structure of your proposals with sections like Executive Summary, Scope of Work, Timeline, and Pricing.

SQL
-- Supabase SQL for templates table
CREATE TABLE proposal_templates (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  name TEXT NOT NULL,
  description TEXT,
  sections JSONB NOT NULL DEFAULT '[]',
  industry TEXT,
  created_at TIMESTAMPTZ DEFAULT now(),
  updated_at TIMESTAMPTZ DEFAULT now()
);

-- Example section structure in JSONB
-- [
--   { "name": "Executive Summary", "prompt_hint": "Brief overview of proposed solution" },
--   { "name": "Scope of Work", "prompt_hint": "Detailed deliverables and milestones" },
--   { "name": "Timeline", "prompt_hint": "Project phases with dates" },
--   { "name": "Investment", "prompt_hint": "Pricing and payment terms" }
-- ]

Phase 2: Client Intake Form

Build a multi-step form that captures all the information Claude needs to generate a relevant proposal. Include fields for company info, project requirements, budget range, and timeline expectations.

TypeScript
// types/proposal.ts
export interface ClientIntake {
  // Company Information
  companyName: string;
  industry: string;
  companySize: 'startup' | 'smb' | 'enterprise';

  // Project Details
  projectType: string;
  problemStatement: string;
  desiredOutcomes: string[];

  // Constraints
  budgetRange: { min: number; max: number };
  timelineWeeks: number;

  // Contact
  contactName: string;
  contactEmail: string;
}

Phase 3: AI Content Generation

This is where Claude transforms client intake data into compelling proposal content. The key is crafting prompts that produce professional, specific, and persuasive text.

TypeScript
// app/api/generate-proposal/route.ts
import Anthropic from '@anthropic-ai/sdk';

const anthropic = new Anthropic();

export async function POST(request: Request) {
  const { intake, template } = await request.json();

  const sections = await Promise.all(
    template.sections.map(async (section: Section) => {
      const response = await anthropic.messages.create({
        model: 'claude-sonnet-4-20250514',
        max_tokens: 1024,
        messages: [{
          role: 'user',
          content: buildSectionPrompt(section, intake)
        }]
      });

      return {
        name: section.name,
        content: response.content[0].text
      };
    })
  );

  return Response.json({ sections });
}
Claude Prompt Engineering

The prompt structure is critical for quality output. See the detailed prompt templates in the next section.

Claude Proposal Writing Prompts

Here are the optimized prompts for each proposal section:

TypeScript
// lib/prompts.ts

export const PROPOSAL_PROMPTS = {
  executiveSummary: (intake: ClientIntake) => `
You are a senior business consultant writing a proposal for ${intake.companyName}.

Write a compelling Executive Summary (250-350 words) that:
1. Acknowledges their challenge: "${intake.problemStatement}"
2. Positions our solution as the ideal fit
3. Highlights 2-3 key benefits aligned with their goals
4. Creates urgency without being pushy
5. Ends with a confident value proposition

Company context:
- Industry: ${intake.industry}
- Size: ${intake.companySize}
- Desired outcomes: ${intake.desiredOutcomes.join(', ')}

Write in a professional but warm tone. Use "we" for our firm and "you" for the client.
Avoid jargon. Be specific, not generic.`,

  scopeOfWork: (intake: ClientIntake) => `
You are a project manager drafting the Scope of Work for ${intake.companyName}.

Create a detailed Scope of Work that includes:

## Deliverables
List 5-7 specific deliverables based on: "${intake.problemStatement}"
Each deliverable should be measurable and tied to their outcomes.

## Methodology
Describe our approach in 3-4 phases:
- Discovery & Planning
- Development & Implementation
- Testing & Refinement
- Launch & Handoff

## Out of Scope
List 3-4 items explicitly excluded to set clear boundaries.

## Success Criteria
Define 3-5 measurable success metrics.

Budget context: $${intake.budgetRange.min.toLocaleString()} - $${intake.budgetRange.max.toLocaleString()}
Timeline: ${intake.timelineWeeks} weeks

Be specific to their industry (${intake.industry}) and company size (${intake.companySize}).`,

  timeline: (intake: ClientIntake) => `
Create a project timeline for a ${intake.timelineWeeks}-week engagement with ${intake.companyName}.

Structure as phases with:
- Phase name
- Duration (weeks)
- Key milestones
- Client touchpoints/review sessions

Project type: ${intake.projectType}
Include buffer time for revisions.
Add specific dates assuming project starts 2 weeks from today.

Format as a clear, scannable timeline that builds client confidence.`,

  investment: (intake: ClientIntake) => `
Write the Investment/Pricing section for ${intake.companyName}'s proposal.

Budget range provided: $${intake.budgetRange.min.toLocaleString()} - $${intake.budgetRange.max.toLocaleString()}

Include:
1. **Investment Summary**: Total project investment with brief justification
2. **Payment Schedule**: 3-4 milestone-based payments
3. **What's Included**: Bullet list of everything covered
4. **Optional Add-ons**: 2-3 upgrade options for future phases
5. **Terms**: Standard payment terms (Net 15, etc.)

Position the investment as valuable, not cheap. Connect cost to outcomes.
Use confident language: "Your investment" not "The cost".`
};

export function buildSectionPrompt(
  section: Section,
  intake: ClientIntake
): string {
  const promptBuilder = PROPOSAL_PROMPTS[section.key];
  if (promptBuilder) {
    return promptBuilder(intake);
  }

  // Fallback for custom sections
  return `Write the "${section.name}" section for a proposal to ${intake.companyName}.
Context: ${section.prompt_hint}
Company: ${intake.industry} ${intake.companySize}
Project: ${intake.problemStatement}
Keep it professional, specific, and under 400 words.`;
}

Phase 4: Section-by-Section Editing

Build an editor interface that allows users to review and refine each AI-generated section. Include regeneration options with feedback for Claude.

TypeScript
// components/SectionEditor.tsx
'use client';

import { useState } from 'react';
import { Textarea } from '@/components/ui/textarea';
import { Button } from '@/components/ui/button';

interface SectionEditorProps {
  section: { name: string; content: string };
  onUpdate: (content: string) => void;
  onRegenerate: (feedback: string) => Promise<string>;
}

export function SectionEditor({
  section,
  onUpdate,
  onRegenerate
}: SectionEditorProps) {
  const [content, setContent] = useState(section.content);
  const [feedback, setFeedback] = useState('');
  const [isRegenerating, setIsRegenerating] = useState(false);

  const handleRegenerate = async () => {
    setIsRegenerating(true);
    const newContent = await onRegenerate(feedback);
    setContent(newContent);
    setFeedback('');
    setIsRegenerating(false);
  };

  return (
    <div className="space-y-4 p-6 border rounded-lg">
      <h3 className="text-lg font-semibold">{section.name}</h3>

      <Textarea
        value={content}
        onChange={(e) => {
          setContent(e.target.value);
          onUpdate(e.target.value);
        }}
        rows={12}
        className="font-mono text-sm"
      />

      <div className="flex gap-4">
        <input
          type="text"
          placeholder="Feedback for regeneration (e.g., 'Make it more concise')"
          value={feedback}
          onChange={(e) => setFeedback(e.target.value)}
          className="flex-1 px-3 py-2 border rounded"
        />
        <Button
          onClick={handleRegenerate}
          disabled={isRegenerating}
        >
          {isRegenerating ? 'Regenerating...' : 'Regenerate'}
        </Button>
      </div>
    </div>
  );
}

Phase 5: PDF Export Functionality

Use React-PDF to generate professional PDF documents with your branding. The library renders React components to PDF format.

TypeScript
// components/ProposalPDF.tsx
import {
  Document,
  Page,
  Text,
  View,
  StyleSheet,
  Font
} from '@react-pdf/renderer';

// Register custom fonts for professional look
Font.register({
  family: 'Inter',
  src: 'https://fonts.gstatic.com/s/inter/v12/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZ9hjp-Ek-_EeA.woff2'
});

const styles = StyleSheet.create({
  page: {
    padding: 50,
    fontFamily: 'Inter',
  },
  header: {
    marginBottom: 30,
    borderBottom: '2px solid #4F46E5',
    paddingBottom: 20,
  },
  title: {
    fontSize: 28,
    fontWeight: 'bold',
    color: '#1F2937',
  },
  subtitle: {
    fontSize: 14,
    color: '#6B7280',
    marginTop: 8,
  },
  section: {
    marginBottom: 24,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#4F46E5',
    marginBottom: 12,
  },
  sectionContent: {
    fontSize: 11,
    lineHeight: 1.6,
    color: '#374151',
  },
  footer: {
    position: 'absolute',
    bottom: 30,
    left: 50,
    right: 50,
    textAlign: 'center',
    fontSize: 10,
    color: '#9CA3AF',
  },
});

interface ProposalPDFProps {
  proposal: {
    clientName: string;
    projectTitle: string;
    date: string;
    sections: Array<{ name: string; content: string }>;
  };
}

export function ProposalPDF({ proposal }: ProposalPDFProps) {
  return (
    <Document>
      <Page size="A4" style={styles.page}>
        <View style={styles.header}>
          <Text style={styles.title}>{proposal.projectTitle}</Text>
          <Text style={styles.subtitle}>
            Prepared for {proposal.clientName} | {proposal.date}
          </Text>
        </View>

        {proposal.sections.map((section, index) => (
          <View key={index} style={styles.section}>
            <Text style={styles.sectionTitle}>{section.name}</Text>
            <Text style={styles.sectionContent}>{section.content}</Text>
          </View>
        ))}

        <Text style={styles.footer}>
          Confidential | Page 1 of 1 | Valid for 30 days
        </Text>
      </Page>
    </Document>
  );
}

Phase 6: Proposal Tracking Dashboard

Finally, build a dashboard to track all proposals with status indicators (Draft, Sent, Viewed, Accepted, Declined) and key metrics.

SQL
-- Proposals table with tracking
CREATE TABLE proposals (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  template_id UUID REFERENCES proposal_templates(id),
  client_intake JSONB NOT NULL,
  sections JSONB NOT NULL,
  status TEXT DEFAULT 'draft',
  total_value DECIMAL(10,2),
  sent_at TIMESTAMPTZ,
  viewed_at TIMESTAMPTZ,
  responded_at TIMESTAMPTZ,
  created_at TIMESTAMPTZ DEFAULT now(),
  updated_at TIMESTAMPTZ DEFAULT now()
);

-- Track proposal views
CREATE TABLE proposal_views (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  proposal_id UUID REFERENCES proposals(id),
  viewed_at TIMESTAMPTZ DEFAULT now(),
  ip_address INET,
  user_agent TEXT
);

Common Issues & Solutions

Here are some common issues you might encounter and how to solve them:

React-PDF SSR Issues

React-PDF doesn't work with Server Components. Use dynamic imports with ssr: false to load PDF components only on the client side.

Claude Rate Limits

When generating multiple sections, use Promise.all carefully. Consider adding a small delay between requests or using a queue system for production.

Generic AI Output

If Claude generates generic content, add more specific context to your prompts. Include industry-specific terminology, company size considerations, and explicit instructions to avoid cliches.

Next Steps

Congratulations on building your AI Proposal Generator! Here are some ways to extend the project:

  • Add authentication: Use Supabase Auth to support multiple users and teams
  • Implement e-signatures: Integrate DocuSign or HelloSign for proposal acceptance
  • Build proposal analytics: Track which sections clients spend the most time reading
  • Create a client portal: Let clients view, comment on, and accept proposals online
  • Add CRM integration: Sync proposals with HubSpot or Salesforce
Ready to Go Further?

Want to build more AI-powered tools for your firm? Check out our other guides for more step-by-step tutorials.