DEV Community

Peter Park
Peter Park

Posted on

Building a Cold Email SaaS: Lessons from 6 Months of Solo Development

Six months ago, I was manually writing cold emails for my consulting business. Every single one. Copy-pasting templates, swapping names, tweaking the opening line, hoping it didn't sound robotic.

It was painful. So I built something to fix it.

Today, ColdMailAI is live, serving real users, and generating revenue. Here's everything I learned building it solo.

The Problem That Started It All

I was sending 50+ cold emails a week. My process looked like this:

  1. Find a prospect on LinkedIn
  2. Open a Google Doc template
  3. Manually personalize the first 2 lines
  4. Pray it didn't land in spam
  5. Forget to follow up

The personalization was the bottleneck. Generic templates got ignored. Truly personalized emails got replies — but took 10-15 minutes each.

I needed a tool that could write emails that sounded like me but didn't require me to write each one from scratch.

Tech Stack Decisions

Here's what I landed on and why:

Next.js + Vercel
I wanted to ship fast. Next.js gave me SSR for the landing page (SEO matters for SaaS), API routes for the backend, and Vercel made deployment a non-issue. Zero DevOps overhead.

// My project structure is dead simple
├── app/
│   ├── page.tsx          // Landing page
│   ├── dashboard/        // User dashboard
│   ├── api/
│   │   ├── generate/     // Email generation endpoint
│   │   ├── webhook/      // PayPal webhooks
│   │   └── auth/         // Auth routes
├── lib/
│   ├── openai.ts         // AI service layer
│   ├── prompts.ts        // Prompt templates
│   └── paypal.ts         // Payment logic
Enter fullscreen mode Exit fullscreen mode

OpenAI API (GPT-4o-mini)
I started with GPT-4 but the cost per email was too high for a $9/month product. GPT-4o-mini gave me 95% of the quality at roughly 1/10th the cost. The key insight: the prompt engineering matters more than the model size.

PayPal for Payments
Stripe would have been my first choice, but I needed something that worked globally without a US business entity. PayPal subscriptions were straightforward to set up.

The Hardest Technical Challenge: Prompt Engineering

This is where I spent 60% of my development time. Getting an LLM to write cold emails that don't sound like they were written by AI is hard.

My first attempt:

// v1: Naive approach — terrible results
const prompt = \`Write a cold email to \${prospect.name} 
at \${prospect.company} about our product.\`;
Enter fullscreen mode Exit fullscreen mode

The output was generic, salesy, and screamed "AI wrote this." Nobody would reply to it.

After weeks of iteration, I landed on a multi-step approach:

// v2: Context-enriched generation
async function generateEmail(prospect, userContext) {
  // Step 1: Analyze the prospect's context
  const analysis = await analyzeProspect({
    name: prospect.name,
    company: prospect.company,
    role: prospect.role,
    recentActivity: prospect.linkedinSummary,
  });

  // Step 2: Generate with structured context
  const email = await openai.chat.completions.create({
    model: 'gpt-4o-mini',
    messages: [
      {
        role: 'system',
        content: buildSystemPrompt(userContext.writingStyle),
      },
      {
        role: 'user',
        content: buildUserPrompt(analysis, userContext.product),
      },
    ],
    temperature: 0.7,
    max_tokens: 500,
  });

  return parseEmailResponse(email);
}
Enter fullscreen mode Exit fullscreen mode

Key lessons on prompt engineering for cold emails:

  1. Feeding context beats clever instructions. Instead of telling the AI "write a personalized email," I feed it the prospect's LinkedIn summary, recent posts, and company news. The AI naturally weaves in relevant details.

  2. Temperature 0.7 is the sweet spot. Lower = too formulaic. Higher = too random and sometimes nonsensical.

  3. Few-shot examples from the user's own sent emails dramatically improve tone matching. I let users paste 3-5 of their best emails during onboarding, and use them as style references.

Handling Rate Limits

When a user generates 50 emails in a batch, you hit OpenAI rate limits fast. My solution:

// Simple but effective rate limiter
class EmailQueue {
  constructor(maxConcurrent = 3, delayMs = 200) {
    this.queue = [];
    this.active = 0;
    this.maxConcurrent = maxConcurrent;
    this.delayMs = delayMs;
  }

  async add(task) {
    return new Promise((resolve, reject) => {
      this.queue.push({ task, resolve, reject });
      this.process();
    });
  }

  async process() {
    if (this.active >= this.maxConcurrent || !this.queue.length) return;

    this.active++;
    const { task, resolve, reject } = this.queue.shift();

    try {
      const result = await task();
      resolve(result);
    } catch (err) {
      if (err.status === 429) {
        // Rate limited — back off and retry
        await new Promise(r => setTimeout(r, 2000));
        this.queue.unshift({ task, resolve, reject });
      } else {
        reject(err);
      }
    } finally {
      this.active--;
      setTimeout(() => this.process(), this.delayMs);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Nothing fancy. A queue with exponential backoff on 429s. It handles 95% of cases.

Building the Email Preview System

Users want to see and edit emails before sending. This sounds simple, but the UX details matter:

  • Real-time preview as the AI generates (streaming SSE responses)
  • Inline editing without losing the AI-generated structure
  • Tone adjustment slider (formal ↔ casual) that regenerates specific sections
  • Subject line variants — I generate 3 options and let the user pick

The streaming preview was the most satisfying feature to build. Users see the email "writing itself" in real-time, which also builds trust in the AI output.

What I Learned About Solo SaaS

1. Ship ugly, iterate fast.
My v1 looked terrible. I used basic Tailwind components, no fancy animations. But it worked. The first paying user didn't care about the UI — they cared that the emails got replies.

2. Talk to users constantly.
I added Tawk.to live chat on day one. Every conversation taught me something. Users asked for features I never would have thought of (like the tone slider).

3. Pricing is emotional, not logical.
I agonized over pricing. $9/month for 100 AI-generated emails felt too cheap. But as a solo builder, I'd rather have 100 users at $9 than 10 at $49. Volume validates faster.

4. The marketing is harder than the code.
Building the product took 2 months. Getting the first 10 users took 3 months. SEO, content marketing, Reddit, Product Hunt — it's a different skill set entirely.

Current State

  • Live at getcoldmail.com
  • Pricing: $9/month (100 emails), $29/month (500 emails)
  • Stack: Next.js, Vercel, OpenAI, PayPal
  • Users: Growing steadily through organic search and word of mouth

What's Next

I'm working on:

  • Team collaboration — shared templates, team analytics
  • CRM integration — HubSpot and Pipedrive first
  • Email tracking — open rates, reply detection
  • A/B testing — automatically test different email variants

Final Thoughts

Building a SaaS solo is equal parts exhilarating and exhausting. There are days when everything breaks and you question why you didn't just keep using Gmail templates.

But then a user messages you: "I got 3 meetings from the emails your tool generated this week."

That makes it worth it.


If you're struggling with cold outreach, check it out at getcoldmail.com. And if you're building something solo — keep shipping. The hardest part isn't the code, it's staying consistent.

What's your biggest challenge with cold email? Drop a comment below.

Top comments (0)