Skill: alirezarezvani/cs-cold-email (version 1.0.0)

--- name: "cold-email" description: "When the user wants to write, improve, or build a sequence of B2B cold outreach emails to prospects who haven't asked to hear from them. Use when the user mentions 'cold email,' 'cold outreach,' 'prospecting emails,' 'SDR emails,' 'sales emails,' 'first touch email,' 'follow-up sequence,' or 'email prospecting.' Also use when they share an email draft that sounds too sales-y and needs to be humanized. Distinct from email-sequence (lifecycle/nurture to opted-in subscribers) — this is unsolicited outreach to new prospects. NOT for lifecycle emails, newsletters, or drip campaigns (use email-sequence)." license: MIT metadata: version: 1.0.0 author: Alireza Rezvani category: marketing updated: 2026-03-06 --- # Cold Email Outreach You are an expert in B2B cold email outreach. Your goal is to help write, build, and iterate on cold email sequences that sound like they came from a thoughtful human — not a sales machine — and actually get replies. ## Before Starting **Check for context first:** If `marketing-context.md` exists, read it before asking questions. Gather this context: ### 1. The Sender - Who are they at this company? (Role, seniority — affects how they write) - What do they sell and who buys it? - Do they have any real customer results or proof points they can reference? - Are they sending as an individual or as a company? ### 2. The Prospect - Who is the target? (Job title, company type, company size) - What problem does this person likely have that the sender can solve? - Is there a specific trigger or reason to reach out now? (funding, hiring, news, tech stack signal) - Do they have specific names and companies to personalize to, or is this a template for a segment? ### 3. The Ask - What's the goal of the first email? (Book a call? Get a reply? Get a referral?) - How aggressive is the timeline? (SDR with daily send volume vs founder doing targeted outreach) --- ## How This Skill Works ### Mode 1: Write the First Email When they need a single first-touch email or a template for a segment. 1. Understand the ICP, the problem, and the trigger 2. Choose the right framework (see `references/frameworks.md`) 3. Draft first email: subject line, opener, body, CTA 4. Review against the principles below — cut anything that doesn't earn its place 5. Deliver: email copy + 2-3 subject line variants + brief rationale ### Mode 2: Build a Follow-Up Sequence When they need a multi-email sequence (typically 4-6 emails). 1. Start with the first email (Mode 1) 2. Plan follow-up angles — each email needs a different angle, not just a nudge 3. Set the gap cadence (Day 1, Day 4, Day 9, Day 16, Day 25) 4. Write each follow-up with a standalone hook that doesn't require reading previous emails 5. End with a breakup email that closes the loop professionally 6. Deliver: full sequence with send gaps, subject lines, and brief on what each email does ### Mode 3: Iterate from Performance Data When they have an active sequence and want to improve it. 1. Review their current sequence emails and performance (open rate, reply rate) 2. Diagnose: is the problem subject lines (low open rate), email body (opens but no replies), or CTA (replies but wrong outcome)? 3. Rewrite the underperforming element 4. Deliver: revised emails + diagnosis + test recommendation --- ## Core Writing Principles ### 1. Write Like a Peer, Not a Vendor The moment your email sounds like marketing copy, it's over. Think about how you'd actually email a smart colleague at another company who you want to have a conversation with. **The test:** Would a friend send this to another friend in business? If the answer is no — rewrite it. - ❌ "I'm reaching out because our platform helps companies like yours achieve unprecedented growth..." - ✅ "Noticed you're scaling your SDR team — timing question: are you doing outbound email in-house or using an agency?" ### 2. Every Sentence Earns Its Place Cold email is the wrong place to be thorough. Every sentence should do one of these jobs: create curiosity, establish relevance, build credibility, or drive to the ask. If a sentence doesn't do one of those — cut it. Read your draft aloud. The moment you hear yourself droning, stop and cut. ### 3. Personalization Must Connect to the Problem Generic personalization is worse than none. "I saw you went to MIT" followed by a pitch has nothing to do with MIT. That's fake personalization. Real personalization: "I saw you're hiring three SDRs — usually a signal that you're trying to scale cold outreach. That's exactly the challenge we help with." The personalization must connect to the reason you're reaching out. ### 4. Lead With Their World, Not Yours The opener should be about them — their situation, their problem, their context. Not about you or your product. - ❌ "We're a sales intelligence platform that..." - ✅ "Your recent TechCrunch piece mentioned you're entering the SMB market — that transition is notoriously hard to do with an enterprise-built playbook." ### 5. One Ask Per Email Don't ask them to book a call, watch a demo, read a case study, AND reply with their timeline. Pick one ask. The more you ask for, the less likely any of it happens. --- ## Voice Calibration by Audience Adjust tone, length, and specificity based on who you're writing to: | Audience | Length | Tone | Subject Line Style | What Works | |----------|--------|------|-------------------|------------| | C-suite (CEO, CRO, CMO) | 3-4 sentences | Ultra-brief, peer-level, strategic | Short, vague, internal-looking | Big problem → relevant proof → one question | | VP / Director | 5-7 sentences | Direct, metrics-conscious | Slightly more specific | Specific observation + clear business angle | | Mid-level (Manager, Analyst) | 7-10 sentences | Practical, shows you did homework | Can be more descriptive | Specific problem + practical value + easy CTA | | Technical (Engineer, Architect) | 7-10 sentences | Precise, no fluff | Technical specificity | Exact problem → precise solution → low-friction ask | The higher up the org chart, the shorter your email needs to be. A CEO gets 100+ emails per day. Three sentences and a clear question is a gift, not a slight. --- ## Subject Lines: The Anti-Marketing Approach The goal of a subject line is to get the email opened — not to convey value, not to be clever, not to impress anyone. Just open it. The best cold email subject lines look like internal emails. They're short, slightly vague, and create just enough curiosity to click. ### What Works | Pattern | Example | Why It Works | |---------|---------|-------------| | Two or three words | `quick question` | Looks like an actual email from a colleague | | Specific trigger + question | `your TechCrunch piece` | Specific enough to not look like spam | | Shared context | `re: Series B` | Feels like a follow-up, not cold | | Observation | `your ATS setup` | Specific, relevant, not salesy | | Referral hook | `[mutual name] suggested I reach out` | Social proof front-loaded | ### What Kills Opens - ALL CAPS anything - Emojis in subject lines (polarizing, often spam-filtered) - Fake Re: or Fwd: (people have learned this trick — it damages trust) - Asking a question in the subject line (e.g., "Are you struggling with X?") — sounds like an ad - Mentioning your company name ("Acme Corp: helping you achieve...") - Numbers that feel like blog headlines ("5 ways to improve your...") --- ## Follow-Up Strategy Most deals happen in follow-ups. Most follow-ups are useless. The difference is whether the follow-up adds value or just creates noise. ### Cadence | Email | Send Day | Gap | |-------|----------|-----| | Email 1 | Day 1 | — | | Email 2 | Day 4 | +3 days | | Email 3 | Day 9 | +5 days | | Email 4 | Day 16 | +7 days | | Email 5 | Day 25 | +9 days | | Breakup | Day 35 | +10 days | Gaps increase over time. You're persistent but not annoying. ### Follow-Up Rules **Each follow-up must have a new angle.** Rotate through: - New piece of evidence (case study, data point, recent result) - New angle on the problem (a different pain point in their world) - Related insight (something you noticed about their industry, tech stack, or news) - Direct question (just ask plainly — sometimes clarity cuts through) - Reverse ask (ask for referral to the right person if you can't reach them) **Never "just check in."** "Just following up to see if you had a chance to read my last email" is a waste of both your time and theirs. If you have nothing new to add, don't send the email. **Don't reference all previous emails.** Each follow-up should stand alone. The prospect doesn't remember your earlier emails. Don't make them scroll. ### The Breakup Email The last email in a sequence should close the loop professionally. It signals this is the last one — which paradoxically increases reply rate because people don't like loose ends. Example breakup: > "I'll stop cluttering your inbox after this one. If [problem] ever becomes a priority, happy to reconnect — just reply here and I'll pick it up. > > If there's someone else at [Company] I should speak with, a name would go a long way. > > Either way — good luck with [whatever's relevant]." See `references/follow-up-playbook.md` for full cadence templates and angle rotation guide. --- ## What to Avoid These are not suggestions — they're patterns that mark you as a non-human and kill reply rates: | ❌ Avoid | Why It Fails | |----------|-------------| | "I hope this email finds you well" | Instant tell that this is templated. Cut it. | | "I wanted to reach out because..." | 3-word delay before actually saying anything | | Feature dump in email 1 | Nobody cares about features when they don't trust you yet | | HTML templates with logos and colors | Looks like marketing, gets spam-filtered | | Fake Re:/Fwd: subject lines | Feels deceptive — kills trust before the first word | | "Just checking in" follow-ups | Adds no value, removes credibility | | Opening with "My name is X and I work at Y" | They can see your name. Start with something interesting. | | Social proof that doesn't connect to their problem | "We work with 500 companies" means nothing without context | | Long-form case study in email 1 | Save it for follow-up when they've shown interest | | Passive CTAs ("Let me know if you're interested") | Weak. Ask a direct question or propose a specific next step. | --- ## Deliverability Basics A great email sent from a flagged domain never lands. Basics you need to have in place: - **Dedicated sending domain** — don't send cold email from your primary domain. Use `mail.yourdomain.com` or `outreach.yourdomain.com`. - **SPF, DKIM, DMARC** — all three must be configured and passing. Use mail-tester.com to verify. - **Domain warmup** — new domains need 4-6 weeks of warmup (start with 20/day, ramp up over time). - **Plain text emails** — or minimal HTML. Heavy HTML triggers spam filters. - **Unsubscribe mechanism** — required legally (CAN-SPAM, GDPR). Include a simple opt-out. - **Sending limits** — stay under 100-200 emails/day per domain until established reputation. - **Bounce rate** — above 5% hurts deliverability. Verify email lists before sending. See `references/deliverability-guide.md` for domain warmup schedule, SPF/DKIM setup, and spam trigger word list. --- ## Proactive Triggers Surface these without being asked: - **Email opens with "My name is" or "I'm reaching out because"** → rewrite the opener. These are dead-on-arrival openers. Flag and offer an alternative that leads with their world. - **First email is longer than 150 words** → almost certainly too long. Flag word count and offer to trim. - **No personalization beyond first name** → templated feel will hurt reply rates. Ask if there's a trigger or signal they can work with. - **Follow-up says "just checking in" or "circling back"** → useless follow-up. Ask what new angle or value they can bring to that touchpoint. - **HTML email template** → recommend plain text. Plain text emails have higher deliverability and look less like marketing blasts. - **CTA asks for 30-45 minute meeting in email 1** → too high-friction for cold outreach. Recommend a lower-commitment ask (a 15-minute call, or just a question to gauge interest first). --- ## Output Artifacts | When you ask for... | You get... | |---------------------|------------| | Write a cold email | First-touch email + 3 subject line variants + brief rationale for structure choices | | Build a sequence | 5-6 email sequence with send gaps, subject lines per email, and angle summary for each follow-up | | Critique my email | Line-by-line assessment + rewrite + explanation of each change | | Write follow-ups only | Follow-up emails 2-6 with unique angles per email + breakup email | | Analyze sequence performance | Diagnosis of where the sequence breaks (subject/body/CTA) + specific rewrite recommendations | --- ## Communication All output follows the structured communication standard: - **Bottom line first** — answer before explanation - **What + Why + How** — every finding has all three - **Actions have owners and deadlines** — no "we should consider" - **Confidence tagging** — 🟢 verified / 🟡 medium / 🔴 assumed --- ## Related Skills - **email-sequence**: For lifecycle and nurture emails to opted-in subscribers. Use email-sequence for onboarding flows, re-engagement campaigns, and automated drips. NOT for cold outreach — that's cold-email. - **copywriting**: For marketing page copy. Principles overlap, but cold email has different constraints — shorter, no CTAs like buttons, must feel personal. - **content-strategy**: For creating the content assets (case studies, guides) you reference in cold email follow-ups. Good follow-up sequences often link to content. - **marketing-strategy-pmm**: For positioning and ICP definition. If you don't know who you're targeting and why, cold email is the wrong tool to figure that out.

path: references/deliverability-guide.md
content: # Deliverability Guide

A cold email that lands in spam is worse than no email at all — it damages your sender reputation for future sends. Get deliverability right before you worry about copy.

---

## The Deliverability Stack

Email deliverability is a layer cake. Every layer has to be correct:

```
Domain reputation      (is your domain trusted by inbox providers?)
        ↓
Authentication         (SPF, DKIM, DMARC — are you who you say you are?)
        ↓
Sending infrastructure (IP reputation, sending limits, ramp-up)
        ↓
List quality           (are you sending to real, active addresses?)
        ↓
Email content          (does the content look like spam?)
        ↓
Engagement signals     (opens, replies, not-spam clicks)
```

Fix problems from the bottom up. No point perfecting copy if your domain is blacklisted.

---

## Domain Setup

### Use a Dedicated Sending Domain

Never send cold email from your primary company domain (`acme.com`). If your cold email domain gets flagged or blacklisted, you lose your main domain's email reputation.

**Setup options:**
- `mail.acme.com` — subdomain of main domain
- `acme-hq.com` — separate domain with similar name
- `getacme.com` / `tryacme.com` — common pattern for SaaS

**Rules for the sending domain:**
- Set up a proper website (even a simple redirect to main site) — bare domains look suspicious
- Match the company name visually — unrelated domains look like phishing
- Get a G Suite / Microsoft 365 mailbox on it — shared hosting email servers have worse reputation

### SPF Record

SPF (Sender Policy Framework) tells receiving servers which IP addresses are allowed to send email from your domain. Without it, your emails look unauthenticated.

**DNS TXT record:**
```
v=spf1 include:_spf.google.com ~all
```

Replace `_spf.google.com` with your sending provider's SPF include. Check your provider's documentation for the exact value (Google Workspace, SendGrid, Mailgun, etc. all have their own).

**Important:** Only have ONE SPF record per domain. If you have multiple, they conflict and authentication fails.

### DKIM

DKIM (DomainKeys Identified Mail) adds a cryptographic signature to your emails, proving they weren't tampered with in transit.

Setup is done through your email provider — they give you a DNS TXT record to add. It looks like:

```
google._domainkey.yourdomain.com  IN  TXT  "v=DKIM1; k=rsa; p=MIGfMA0..."
```

The public key in that record lets receiving servers verify your email's signature.

### DMARC

DMARC ties SPF and DKIM together and tells receiving servers what to do when authentication fails.

**Starter DMARC record (monitoring mode):**
```
_dmarc.yourdomain.com  IN  TXT  "v=DMARC1; p=none; rua=mailto:[REDACTED_SECRET]"
```

`p=none` means monitor but don't block — good to start with. Once you've confirmed SPF and DKIM are working cleanly, move to `p=quarantine` or `p=reject`.

### Verify Everything

Use **mail-tester.com**: send a test email to their address, then check your score. 9/10 or higher means your authentication is clean. Below 7/10 means something is broken.

---

## Domain Warmup

A brand new domain has no sending reputation. Email providers don't trust it. If you start sending 200 emails/day on day one, you will be flagged.

Warmup = building reputation gradually by sending low volumes and getting positive engagement.

### Warmup Schedule

| Week | Emails/Day | Focus |
|------|-----------|-------|
| 1 | 5-10 | Real conversations only — send to colleagues, get replies |
| 2 | 20-30 | Small cold outreach batches — highly targeted, good lists |
| 3 | 40-60 | Expand slightly — maintain >30% open rate |
| 4 | 80-100 | Normal volume — watch bounce and spam complaint rates |
| 5+ | Up to 200 | Full volume — monitor daily |

**Warning signs that warmup is failing:**
- Open rate drops below 20%
- Bounce rate above 3%
- Spam complaint rate above 0.1%
- Emails landing in Gmail Promotions tab

**Manual warmup vs tools:** Tools like Lemwarm, Warmup Inbox, or Mailreach automate warmup by sending emails to a network of inboxes that automatically open and engage. These help build reputation faster. They're worth it for new domains.

---

## List Quality

Sending to bad email addresses destroys your sender reputation. Every hard bounce tells inbox providers your list is dirty.

### Before Sending

1. **Verify email addresses** — Use a verification tool (NeverBounce, ZeroBounce, Hunter's verify, etc.) before importing any list. Remove invalid, catch-all, and risky emails.

2. **Target bounce rate:** Keep it below 2%. Above 5% is dangerous territory.

3. **Remove catch-all domains carefully** — Catch-all domains accept any email regardless of whether the mailbox exists. Your emails won't hard-bounce, but they may go nowhere.

4. **Never buy lists** — Purchased lists are old, dirty, unverified, and frequently include spam traps (addresses placed by inbox providers to catch spammers). One spam trap hit can blacklist your domain.

### Ongoing Hygiene

- Remove anyone who hasn't opened in 90 days from your sequence (move to a re-engagement campaign or suppress)
- Remove unsubscribes immediately — required legally and good for reputation
- Remove bounces from all future sends automatically

---

## Content That Hurts Deliverability

Spam filters evaluate content alongside authentication and reputation. These patterns trigger filters:

### Spam Trigger Words to Avoid

High-risk words and phrases (use sparingly or avoid):
- "Free" (especially in subject lines)
- "Guaranteed" / "100% guaranteed"
- "No obligation"
- "Act now" / "Limited time"
- "Congratulations"
- "You've been selected"
- "Click here"
- "Earn money" / "Make money"
- "Risk-free"
- "Special offer"
- Excessive exclamation points!!!
- ALL CAPS words

These don't automatically spam-filter you, but they're additive — the more of them in a single email, the higher the spam score.

### Content Rules

| Do | Don't |
|----|-------|
| Plain text or minimal HTML | Heavy HTML with complex tables, images |
| One link max per email | 5+ links — looks like phishing or newsletter |
| Personalized subject lines | Batch-blasted "LAST CHANCE" subject lines |
| Unsubscribe link | No unsubscribe mechanism |
| Consistent from name | Rotating from names |
| Short emails | Wall-of-text emails |

### The HTML Question

Plain text emails consistently get better deliverability than HTML emails for cold outreach. They look like real emails from real people — because they are.

If you need to include your company logo and a fancy template: don't. Save that for newsletters to opted-in subscribers. Cold email = plain text, signed like a person.

---

## Sending Limits by Platform

| Platform | Safe Daily Volume | Notes |
|----------|------------------|-------|
| Google Workspace (paid) | 500/day | Shared across all outgoing |
| Google Workspace + Warmup | Up to 2000/day | After full warmup |
| Microsoft 365 | 10,000/day | Generous, but still subject to reputation |
| SendGrid | Depends on plan | IP reputation matters at scale |
| Mailgun | Depends on plan | Good for transactional, OK for cold |
| Lemlist / Instantly / Apollo | Platform-managed | Warmup built in, use their sending infrastructure |

For cold outreach at scale (>500/day), dedicated sending platforms are better than Google/Microsoft direct — they're designed to manage reputation across many users.

---

## Checking Your Reputation

If you suspect deliverability problems, check these:

1. **Mail-tester.com** — Authentication and content score (10/10 is perfect)
2. **MXToolbox Blacklist Check** — Check if your domain or IP is on any blacklists
3. **Google Postmaster Tools** — Shows your domain reputation with Gmail (spam rate, auth failures)
4. **Microsoft SNDS** — Similar to Google Postmaster for Outlook/Hotmail

**If you're on a blacklist:**
- Stop sending immediately from that domain
- Identify the cause (bad list, spam complaints, warmup failure)
- Follow the blacklist's delisting process (each has its own)
- Consider using a new domain while the old one recovers

---

## Legal Requirements

Cold email has legal requirements in most markets. Breaking them isn't just unethical — it's fined.

| Regulation | Where | Key Requirements |
|-----------|-------|-----------------|
| CAN-SPAM | USA | Honest subject line, physical address, unsubscribe mechanism |
| CASL | Canada | Requires express or implied consent — much stricter than CAN-SPAM |
| GDPR | EU/EEA | Legitimate interest basis required; no soft opt-in |
| PECR | UK | Similar to GDPR; ICO enforcement |

**Minimum compliance for most cold email:**
- Include your company name and physical address in every email
- Provide a working unsubscribe link or reply-to-unsubscribe instruction
- Honor unsubscribes within 10 business days (CAN-SPAM) or immediately (GDPR best practice)
- Don't use misleading subject lines or from names

**Disclaimer:** This is practical guidance, not legal advice. For EU/Canada outreach, consult a lawyer who specializes in email marketing law — GDPR and CASL are stricter than most people realize.


path: references/follow-up-playbook.md
content: # Follow-Up Playbook

Full cadence guide, angle rotation, and breakup email templates. The goal: stay persistent without becoming noise.

---

## The Core Problem with Follow-Ups

Most follow-up emails are a form of wishful thinking: "Maybe they missed it. I'll send it again." They didn't miss it. They read it, didn't feel urgency, and moved on. Another "just checking in" doesn't create urgency — it signals that you have nothing new to offer.

The only follow-up worth sending is one that adds something: a new angle, a new proof point, a new question, or a new frame.

---

## The Full Cadence

| Email | Label | Day | Gap | Purpose |
|-------|-------|-----|-----|---------|
| 1 | First touch | Day 1 | — | Lead with their world, establish relevance |
| 2 | New angle | Day 4 | +3 | Different problem angle or social proof |
| 3 | Value add | Day 9 | +5 | Resource, insight, or data point |
| 4 | Direct question | Day 16 | +7 | Cut through with a plain, direct ask |
| 5 | Reverse | Day 25 | +9 | Ask for referral to the right person |
| 6 | Breakup | Day 35 | +10 | Close the loop, leave door open |

Gaps increase over time. You're persistent but not desperate.

6 emails is the upper limit for most cold outreach. For very high-value accounts (ABM-style), you might go to 8. For volume prospecting, 4-5 is often more practical.

---

## Email-by-Email Guide

### Email 1: First Touch
Already covered in `frameworks.md`. The anchor of the sequence.

**What it needs:**
- Specific, relevant opener
- Clear connection between their situation and what you do
- One ask, low friction

---

### Email 2: New Angle (Day 4)

This is where most sequences fail — they send a "following up" reminder. Don't.

Email 2 should approach the problem from a different angle than Email 1. If Email 1 was about their hiring signal, Email 2 might be about the operational risk that follows from rapid hiring. Different angle, same direction.

**Angle options for Email 2:**
- Different pain point in the same domain
- Social proof / customer story that's highly relevant to their context
- An industry trend that makes the problem more urgent
- A specific, relevant statistic you haven't mentioned yet

**Template structure:**
```
[New angle or observation — 1-2 sentences]
[Expand on why it matters for their situation — 1-2 sentences]
[Soft CTA — question or invitation]
```

**Example:**
> A lot of the teams I talk to at your stage are hitting the same wall: the ramp time on new SDRs has stretched from 3 months to 5+ months because the playbook that worked at 5 reps doesn't scale to 15.
>
> It's not a hiring problem — it's an enablement infrastructure problem that doesn't become visible until you're already in it.
>
> Is that a challenge you're actively working on, or is it on the radar for later?

---

### Email 3: Value Add (Day 9)

Give something useful before asking again. This builds goodwill and separates you from the other 30 emails in their inbox that only ask.

**What counts as value:**
- A relevant guide, benchmark report, or template (if you have one)
- A specific insight about their market or competitor landscape
- A practical suggestion based on what you know about their situation
- A useful question that helps them think about their problem differently

**Template structure:**
```
[Reference to something specific about them — 1 sentence]
[The value: insight, resource, or useful observation — 2-3 sentences]
[Low-friction CTA: "useful?" or "happy to elaborate" or specific ask]
```

**Example:**
> We just published a benchmark of SDR ramp times across 40 SaaS companies by stage — the data is pretty surprising (the fastest don't hire the most experienced reps, they do onboarding completely differently).
>
> Thought of your situation when reviewing it. Happy to share the relevant section if useful — no strings, just might be helpful context for where you're headed.

---

### Email 4: Direct Question (Day 16)

By Email 4, subtlety has run its course. Sometimes the most effective move is to ask a direct, plain question. No setup, no story.

This email is short. Often just two or three lines.

**Options:**
- Ask what's getting in the way
- Ask if your assumption about their problem is wrong
- Ask if the timing just isn't right
- Ask who the right person to talk to is

**Template structure:**
```
[One direct question — sometimes that's all this email needs to be]
[Optional: one sentence of context if needed]
[Nothing else]
```

**Example:**
> Is SDR ramp time actually a priority for you right now, or is the timing just off?
>
> No judgment either way — just helps me know whether it's worth staying in touch.

Or even shorter:
> Am I reaching the wrong person here — is there someone else on your team who owns sales enablement?

---

### Email 5: Reverse / Referral (Day 25)

If you haven't reached the right person, this email shifts to asking for the referral. If you have reached the right person but they haven't replied, the referral ask sometimes unlocks a conversation because it's a different and lower-commitment request.

**Template structure:**
```
[Acknowledge you may not be reaching the right person — 1 sentence]
[Who you're actually looking for — specific role or function — 1 sentence]
[Referral ask — 1 sentence]
```

**Example:**
> I might be reaching out to the wrong person — the conversations I typically have are with whoever owns sales onboarding and enablement, which may not be you.
>
> If there's a name you could point me toward, I'd really appreciate it. And if it is you — totally understand if the timing isn't right.

---

### Email 6: Breakup (Day 35)

The last email. Its job is to close the loop professionally and leave the relationship in a better place than if you'd just gone silent.

The breakup email often generates the highest reply rate of the entire sequence — people don't like unanswered threads.

**What makes a good breakup:**
- Signals clearly that this is the last one (without being dramatic about it)
- Leaves the door open — no hard feelings
- Offers one final path to action (reply, referral, or reconnect later)
- Keeps it under 5 sentences

**Template:**
```
[Signal this is your last email — 1 sentence]
[Genuine offer to reconnect when timing changes — 1 sentence]
[Referral ask as a final option — 1 sentence]
[Warm close — 1 sentence]
```

**Example:**
> I'll stop cluttering your inbox after this one.
>
> If scaling your outbound motion ever becomes a priority, happy to pick this back up — just reply here and I'll be there.
>
> If there's someone else at [Company] who owns this, a name would be genuinely helpful.
>
> Either way, good luck with the Berlin expansion.

**What to avoid in the breakup:**
- Passive-aggressive tone ("I've tried to reach you several times now...")
- Fake urgency ("This is your last chance to...")
- Asking for feedback on why they didn't reply (annoying, not useful)

---

## Angle Rotation Guide

Never repeat the same angle twice. Here are enough angles for a full 6-email sequence on any B2B topic:

| Angle | Description | Example |
|-------|------------|---------|
| Trigger event | The specific reason you reached out | "Saw the funding announcement..." |
| Adjacent pain | A related problem they also likely have | "The challenge after that usually is..." |
| Social proof | Customer story or result | "We helped a team in your situation..." |
| Industry trend | External force making the problem more urgent | "EMEA data residency rules are tightening..." |
| Data/benchmark | A specific number that reframes the problem | "The average ramp time in your segment is 4.2 months..." |
| Counterintuitive insight | Something most people in their role get wrong | "Most teams solve this by hiring more, which makes it worse..." |
| Resource offer | Something genuinely useful, no strings | "We just published a guide on exactly this..." |
| Direct question | Plain, honest ask | "Is this even a priority right now?" |
| Referral ask | Ask for the right person if not them | "Am I talking to the right person here?" |
| Breakup | Close the loop | "I'll stop after this one..." |

Sequence design tip: never use two heavy asks back to back. Pattern: trigger → social proof → value → direct → referral → breakup works better than ask → ask → ask → ask → ask → breakup.

---

## Short Sequence Variations

### 3-Email Sequence (High-Volume SDR)
1. Day 1: OPPA framework (trigger + problem + proof + ask)
2. Day 5: Value add (resource, insight, or data point)
3. Day 12: Breakup

### 4-Email Sequence (Balanced)
1. Day 1: First touch
2. Day 4: New angle / social proof
3. Day 10: Direct question
4. Day 20: Breakup

### 6-Email Sequence (ABM / High-Value Accounts)
Full sequence as described above.

---

## What Never to Send

- **"Just following up"** — Adds nothing. Deletes itself.
- **"Did you see my last email?"** — They saw it. This is passive-aggressive.
- **"I wanted to make sure this didn't get lost"** — Patronizing.
- **"I know you're busy but..."** — Everyone's busy. Don't invoke it.
- **A forwarded copy of the original email** — They have the original. This is lazy.
- **Back-to-back emails on the same day** — Unless it's a clear error correction.


path: references/frameworks.md
content: # Cold Email Outreach Frameworks

Three frameworks that work, when to use each, and how to apply them with examples.

---

## How to Use This Guide

A framework is a structure, not a script. Use it to organize your thinking, then write in your own voice. If an email sounds like it was written from a template, the framework failed.

Each framework works best in specific situations — the mismatch between framework and context is why most cold emails fall flat.

---

## Framework 1: Observation → Problem → Proof → Ask (OPPA)

**Best for:** Prospects where you have a specific, real observation (trigger event, signal, public info). This is the most versatile framework and the default for most B2B cold email.

**What it does:** Starts with something real and specific about them, connects it to a problem they likely have, brings in credibility, and asks a focused question.

### Structure

```
[Observation]: Something specific and true about them right now.
[Problem]:     The logical challenge or risk that creates.
[Proof]:       One concrete piece of evidence you can solve it.
[Ask]:         A single, low-friction question or request.
```

### How to Write Each Part

**Observation** — This must be:
- Specific (not "I see you're in the software industry")
- Recent (not something from 2 years ago)
- Relevant to the problem you're about to raise
- Non-creepy (public info: LinkedIn, press, job postings, tech stack signals)

Good observations:
- "Saw the announcement that you're opening a Berlin office."
- "Noticed you're hiring 4 SDRs simultaneously — unusual to scale the team that fast."
- "Your last three blog posts have all been about compliance — guessing that's a pressure point right now."

**Problem** — This should feel like something they already know is true, not something you're trying to convince them of.

- ❌ "Companies like yours struggle with X."
- ✅ "That scale-up usually surfaces a bunch of process gaps that are invisible when you're smaller."

**Proof** — Keep it tight. One result, one customer name (if allowed), or one specific claim. Not a list.

- ❌ "We work with 300+ companies and have won 7 awards."
- ✅ "We helped a similar-sized team in fintech cut SDR ramp time by 40% in the first quarter."

**Ask** — One ask. Low friction. Makes it easy to say yes or no.

- ❌ "Would you be open to a 45-minute product walkthrough with our sales team?"
- ✅ "Worth 15 minutes to compare notes on how you're handling this?"

### Full Example

**Subject:** your Berlin expansion

> Congrats on the Berlin announcement — Series B followed by a new market in the same quarter is a big move.
>
> The part that usually bites teams at this stage: the go-to-market motion that worked for your home market rarely translates directly, especially if you're dealing with different buyer personas and a cold pipeline.
>
> We've helped three B2B SaaS teams with exactly this transition — the fastest got pipeline moving in Germany within 90 days. Happy to share what worked.
>
> Worth a 20-minute call to compare notes?

---

## Framework 2: Question → Value → Ask (QVA)

**Best for:** Situations where you don't have a strong trigger event, but you understand the prospect's world well enough to lead with a sharp insight or question. Good for segmented outreach to a persona with a known, common pain.

**What it does:** Opens with a question that creates cognitive engagement — they can't help but answer it in their head. Then delivers value before asking for anything.

### Structure

```
[Question]:  A question they're probably already asking themselves.
[Value]:     An insight, reframe, or resource that helps them — before they've agreed to anything.
[Ask]:       Low-friction request to continue the conversation.
```

### How to Write Each Part

**Question** — Not a rhetorical sales question ("Are you struggling with X?"). An actual, thoughtful question they'd ask at a team meeting.

- ❌ "Are you struggling to hit your pipeline targets?"
- ✅ "What's your current approach to EMEA expansion — inside sales, channel, or hybrid?"

The question works because it's specific enough that only a relevant person can answer it, and answering it in their head pulls them into the email.

**Value** — Give something before asking for anything. This is the differentiator. Options:
- A useful insight from your experience working in their space
- A specific data point or benchmark they probably don't have
- A framework or reframe that's genuinely useful
- A short, actionable observation about their situation

This doesn't need to be long. Two sentences of genuine value beats two paragraphs of soft selling.

**Ask** — Same rules as OPPA. One ask, low friction, specific.

### Full Example

**Subject:** EMEA expansion approach

> Quick question — are you planning to open EMEA with a field sales team, or running it remotely from the US for the first 12 months?
>
> I ask because we've seen both approaches play out across about 30 SaaS companies doing this move, and the one that consistently underperforms is the "remote first, hire local later" model — not because of the sales motion, but because of the support/onboarding gap that follows when you close enterprise deals in a timezone you don't cover.
>
> Happy to share a quick breakdown of what the fastest-scaling teams do differently if that's useful. 15 minutes?

---

## Framework 3: Trigger → Insight → Ask (TIA)

**Best for:** When you have a very specific, time-sensitive trigger event and want to move fast. Great for sales teams with intent signals, tech stack changes, funding news, leadership changes, or industry regulatory shifts.

**What it does:** Names the trigger directly, provides a non-obvious insight about what that trigger means, and asks a focused question while the timing is relevant.

### Structure

```
[Trigger]:  Name the specific event/signal you observed.
[Insight]:  Something non-obvious about what that trigger typically means/leads to.
[Ask]:      Direct, time-aware request.
```

### How to Write Each Part

**Trigger** — Be specific and direct. Don't be coy about why you're reaching out.

- ❌ "I was browsing LinkedIn and happened to notice..."
- ✅ "Saw the funding announcement this morning."

**Insight** — The non-obvious part is what separates this from lazy trigger-based outreach. You're not just saying "congrats on the funding" — you're showing you understand what that trigger means operationally.

Pattern: "That usually means [specific operational challenge] that most [their role] underestimate."

- ❌ "Congrats! We'd love to help you grow."
- ✅ "Series A typically means the first real pressure to build repeatable pipeline — and most companies at this stage haven't yet figured out which channels actually scale."

**Ask** — Frame the timing as genuine, not manufactured urgency.

- ❌ "Act now before it's too late!"
- ✅ "First 60 days post-funding is when this gets set up or doesn't — worth a quick call?"

### Full Example

**Subject:** post-Series A pipeline

> Saw the Series A close — congrats.
>
> The next 90 days are when pipeline architecture either gets built properly or gets bolted together in a way that causes problems at Series B. Most founders don't realize until 18 months later that they're paying for shortcuts made now.
>
> We work specifically with post-Series A B2B SaaS teams setting up their outbound motion for the first time. Happy to do a no-strings 20-minute call on what works and what doesn't at your stage.
>
> Useful?

---

## Choosing the Right Framework

| Situation | Use |
|-----------|-----|
| Strong trigger event (funding, hiring, news, tech change) | TIA |
| Good persona understanding, no specific trigger | QVA |
| Mix of trigger + problem knowledge | OPPA |
| Referral or warm intro context | OPPA with referral opener |
| Re-engaging a past prospect | QVA with callback to previous context |

## Combining Frameworks

These frameworks aren't rigid. In practice, the best emails blend elements:
- TIA trigger + OPPA proof
- QVA question + TIA timing
- OPPA observation + QVA value

What you can't blend: two questions, two proof points, or two asks. One of each, always.

---

## Subject Line Frameworks

Subject lines have their own logic — separate from the email body.

### The Blank Subject
Two or three words, no capitalization, feels like an internal message.
- `quick question`
- `cold outreach`
- `your q3 pipeline`

### The Named Trigger
Specific enough to signal you did research, vague enough to create curiosity.
- `your Series A`
- `Berlin office`
- `your ATS stack`

### The Shared Context
Implies a pre-existing relationship or shared frame.
- `re: EMEA expansion`
- `following up on the hiring spike`

### The Named Person (Referral)
Only use if the referral is real — never fake this.
- `[Mutual Name] suggested I reach out`
- `[Name] mentioned you're building out your SDR team`

### Never Use
- `Quick question about your [product category] strategy!`
- `Revolutionize your [function] with [product name]`
- `[FIRST NAME], we have a special offer for you`
- Emojis
- ALL CAPS
- Question marks (feels like an ad)


path: scripts/email_sequence_analyzer.py
content: #!/usr/bin/env python3
"""
email_sequence_analyzer.py — Analyzes a cold email sequence for quality signals.

Evaluates each email on:
  - Word count (shorter is usually better for cold email)
  - Reading level estimate (Flesch-Kincaid approximation via avg sentence/word length)
  - Personalization density (signals of specific, targeted writing)
  - CTA clarity (is there a clear ask?)
  - Spam trigger words (words that hurt deliverability)
  - Subject line analysis (length, warning patterns)
  - Overall score: 0-100

Usage:
    python3 email_sequence_analyzer.py [sequence.json]
    cat sequence.json | python3 email_sequence_analyzer.py

If no file provided, runs on embedded sample sequence.

Input format (JSON):
    [
      {
        "email": 1,
        "subject": "...",
        "body": "..."
      },
      ...
    ]

Stdlib only — no external dependencies.
"""

import json
import re
import sys
import math
import select
from typing import List, Dict, Any


# ─── Spam trigger words ───────────────────────────────────────────────────────

SPAM_TRIGGERS = [
    "free", "guaranteed", "no obligation", "act now", "limited time",
    "click here", "earn money", "make money", "risk-free", "special offer",
    "no cost", "winner", "congratulations", "you've been selected",
    "once in a lifetime", "urgent", "don't miss out", "buy now",
    "order now", "100%", "best price", "lowest price", "incredible deal",
    "amazing offer", "cash bonus", "extra cash", "fast cash",
    "you have been chosen", "exclusive deal", "as seen on",
    "dear friend", "valued customer",
]

# ─── Personalization signals ──────────────────────────────────────────────────

PERSONALIZATION_SIGNALS = [
    # Direct references to "you"
    r'\byou(?:r|rs|\'re|\'ve|\'d|\'ll)?\b',
    # Trigger references
    r'\b(?:saw|noticed|read|heard|saw|found|noted)\b',
    # Named observation patterns
    r'\b(?:your team|your company|your role|your work|your recent|your post)\b',
    # Industry/role-specific references
    r'\b(?:as a|in your|at your|given your)\b',
    # Specific numbers or facts
    r'\b\d{4}\b',  # years — often a sign of specific research
    r'\$\d+|\d+%',  # numbers with $ or %
]

# ─── Dead opener phrases ──────────────────────────────────────────────────────

DEAD_OPENERS = [
    "i hope this email finds you well",
    "i hope this finds you",
    "i wanted to reach out",
    "i am reaching out",
    "my name is",
    "i'm writing to",
    "i am writing to",
    "hope you're doing well",
    "i hope you are doing well",
    "just following up",
    "just checking in",
    "circling back",
    "touching base",
    "per my last email",
    "as per my previous",
]

# ─── Weak CTA patterns ────────────────────────────────────────────────────────

WEAK_CTA = [
    "let me know if you're interested",
    "let me know if you would be interested",
    "feel free to",
    "please don't hesitate",
    "if you have any questions",
    "looking forward to hearing from you",
    "i look forward to connecting",
    "hope we can connect",
]

# ─── Strong CTA signals ───────────────────────────────────────────────────────

STRONG_CTA_PATTERNS = [
    r'\b(?:15|20|30|45|60)[\s-]?minute\b',   # time-specific meeting ask
    r'\b(?:call|chat|talk|speak|connect|meet)\b.*\?',  # question + meeting word
    r'worth\s+(?:a|an)\b',                   # "worth a call?"
    r'\?$',                                   # ends with question
    r'\buseful\b\s*\?',                       # "useful?"
    r'\b(?:reply|respond)\b',                 # explicit reply ask
]


# ─── Text utilities ───────────────────────────────────────────────────────────

def count_words(text: str) -> int:
    return len(text.split())


def count_sentences(text: str) -> int:
    """Rough sentence count by terminal punctuation."""
    sentences = re.split(r'[.!?]+', text)
    return max(1, len([s for s in sentences if s.strip()]))


def avg_words_per_sentence(text: str) -> float:
    words = count_words(text)
    sentences = count_sentences(text)
    return words / sentences if sentences else words


def avg_chars_per_word(text: str) -> float:
    words = text.split()
    if not words:
        return 0
    return sum(len(w.strip('.,!?;:')) for w in words) / len(words)


def flesch_reading_ease(text: str) -> float:
    """
    Approximate Flesch Reading Ease score.
    206.835 - 1.015 * (words/sentences) - 84.6 * (syllables/words)
    We approximate syllables as: max(1, len(word) * 0.4) for each word.
    """
    words = text.split()
    if not words:
        return 0
    sentences = count_sentences(text)
    syllables = sum(max(1, int(len(re.sub(r'[^aeiouAEIOU]', '', w)) * 1.2) or 1) for w in words)
    asl = len(words) / sentences  # avg sentence length
    asw = syllables / len(words)  # avg syllables per word
    score = 206.835 - (1.015 * asl) - (84.6 * asw)
    return max(0, min(100, score))


def grade_reading_level(fre_score: float) -> str:
    """Convert Flesch Reading Ease to a human label."""
    if fre_score >= 70:
        return "Easy (conversational)"
    if fre_score >= 60:
        return "Plain English"
    if fre_score >= 50:
        return "Fairly difficult"
    return "Difficult (too complex for cold email)"


# ─── Analysis functions ───────────────────────────────────────────────────────

def analyze_subject_line(subject: str) -> Dict:
    issues = []
    warnings = []

    if not subject:
        return {"length": 0, "issues": ["No subject line provided"], "score": 0}

    length = len(subject)

    if length > 60:
        issues.append(f"Too long ({length} chars) — aim for under 50")
    if length > 50:
        warnings.append("Subject is getting long — shorter subjects get more opens")

    if subject.isupper():
        issues.append("All caps subject lines trigger spam filters")

    if re.search(r'!!!|!{2,}', subject):
        issues.append("Multiple exclamation points look like spam")

    if subject.startswith("Re:") or subject.startswith("Fwd:"):
        lower = subject.lower()
        if lower.startswith("re:") or lower.startswith("fwd:"):
            warnings.append("Fake Re:/Fwd: subjects feel deceptive — people have learned this trick")

    if re.search(r'[A-Z]{4,}', subject) and not subject.isupper():
        warnings.append("SHOUTING words in subject lines look like spam")

    if re.search(r'[\U0001F600-\U0001FFFF]', subject):
        warnings.append("Emojis in subject lines are polarizing and often spam-filtered for B2B")

    if '?' in subject:
        warnings.append("Question mark in subject can feel like an ad — test without")

    # Spam trigger check in subject
    subject_lower = subject.lower()
    triggered = [w for w in SPAM_TRIGGERS if w in subject_lower]
    if triggered:
        issues.append(f"Spam trigger words in subject: {', '.join(triggered)}")

    # Score
    score = 100
    score -= len(issues) * 20
    score -= len(warnings) * 10
    score = max(0, min(100, score))

    return {
        "length": length,
        "issues": issues,
        "warnings": warnings,
        "score": score,
    }


def analyze_body(body: str) -> Dict:
    body_lower = body.lower()
    findings = []
    deductions = []

    word_count = count_words(body)
    fre = flesch_reading_ease(body)
    reading_level = grade_reading_level(fre)
    avg_wps = avg_words_per_sentence(body)

    # Word count scoring
    if word_count > 200:
        deductions.append(("word_count", 15, f"Too long ({word_count} words) — cold emails should be under 150"))
    elif word_count > 150:
        deductions.append(("word_count", 5, f"Getting long ({word_count} words) — aim for under 150"))
    elif word_count < 30:
        deductions.append(("word_count", 10, f"Very short ({word_count} words) — may lack enough context"))

    # Sentence length
    if avg_wps > 25:
        deductions.append(("readability", 10, f"Sentences average {avg_wps:.0f} words — too complex, aim for 15-20"))

    # Dead opener check
    for opener in DEAD_OPENERS:
        if opener in body_lower:
            deductions.append(("opener", 20, f"Dead opener detected: '{opener}' — rewrite the opening"))
            break

    # Personalization density
    pers_matches = 0
    for pattern in PERSONALIZATION_SIGNALS:
        matches = re.findall(pattern, body_lower)
        pers_matches += len(matches)

    pers_density = pers_matches / word_count * 100 if word_count else 0
    if pers_density < 5:
        deductions.append(("personalization", 10, "Low personalization signals — email may feel generic"))

    # Spam trigger words in body
    triggered = [w for w in SPAM_TRIGGERS if w in body_lower]
    if triggered:
        deductions.append(("spam", len(triggered) * 5, f"Spam trigger words: {', '.join(triggered[:5])}"))

    # Weak CTA check
    for weak in WEAK_CTA:
        if weak in body_lower:
            deductions.append(("cta", 10, f"Weak CTA: '{weak}' — be more direct"))
            break

    # Strong CTA check
    has_strong_cta = any(re.search(p, body_lower) for p in STRONG_CTA_PATTERNS)
    if not has_strong_cta:
        deductions.append(("cta", 15, "No clear CTA detected — every cold email needs a single, direct ask"))

    # HTML check
    if re.search(r'<html|<body|<table|<div|style="|font-family:', body_lower):
        deductions.append(("format", 20, "HTML detected — plain text emails get better deliverability for cold outreach"))

    # Multiple links
    links = re.findall(r'https?://', body)
    if len(links) > 2:
        deductions.append(("links", 10, f"{len(links)} links detected — keep to 1-2 max for cold email"))

    # Calculate score
    total_deduction = sum(d[1] for d in deductions)
    score = max(0, min(100, 100 - total_deduction))

    return {
        "word_count": word_count,
        "reading_ease_score": round(fre, 1),
        "reading_level": reading_level,
        "avg_words_per_sentence": round(avg_wps, 1),
        "personalization_density": round(pers_density, 1),
        "has_strong_cta": has_strong_cta,
        "spam_triggers": triggered,
        "deductions": [(d[2], d[1]) for d in deductions],
        "score": score,
    }


# ─── Report printer ───────────────────────────────────────────────────────────

def grade(score: int) -> str:
    if score >= 85:
        return "🟢 Strong"
    if score >= 65:
        return "🟡 Decent"
    if score >= 45:
        return "🟠 Needs work"
    return "🔴 Rewrite"


def print_report(results: List[Dict]) -> None:
    print("\n" + "═" * 64)
    print("  COLD EMAIL SEQUENCE ANALYSIS")
    print("═" * 64)

    scores = []
    for r in results:
        email_num = r["email"]
        subj = r["subject_analysis"]
        body = r["body_analysis"]
        overall = r["overall_score"]
        scores.append(overall)

        print(f"\n── Email {email_num}: \"{r['subject']}\" ──")
        print(f"   Overall: {overall}/100  {grade(overall)}")

        print(f"\n   Subject ({subj['length']} chars): {subj['score']}/100")
        for issue in subj.get("issues", []):
            print(f"   ❌ {issue}")
        for warn in subj.get("warnings", []):
            print(f"   ⚠️  {warn}")

        print(f"\n   Body Analysis:")
        print(f"   Words: {body['word_count']}  |  "
              f"Reading: {body['reading_level']}  |  "
              f"Avg sentence: {body['avg_words_per_sentence']} words  |  "
              f"Personalization density: {body['personalization_density']}%")
        print(f"   CTA: {'✅ Clear ask detected' if body['has_strong_cta'] else '❌ No clear CTA found'}")

        if body.get("spam_triggers"):
            print(f"   ⚠️  Spam triggers: {', '.join(body['spam_triggers'])}")

        if body.get("deductions"):
            print(f"\n   Issues found:")
            for desc, pts in body["deductions"]:
                print(f"   [-{pts:2d}] {desc}")

    avg = sum(scores) // len(scores) if scores else 0
    print(f"\n{'═' * 64}")
    print(f"  SEQUENCE OVERALL: {avg}/100  {grade(avg)}")
    print(f"  Emails analyzed: {len(results)}")

    # Sequence-level observations
    print("\n  Sequence observations:")
    word_counts = [r["body_analysis"]["word_count"] for r in results]
    if all(abs(word_counts[i] - word_counts[i-1]) < 20 for i in range(1, len(word_counts))):
        print("  ⚠️  All emails are similar length — vary length across sequence")

    if len(results) > 1:
        last_body = results[-1]["body_analysis"]
        if last_body["word_count"] > 100:
            print("  ⚠️  Final email (breakup) should be shorter — 3-5 sentences max")

    print("═" * 64 + "\n")


# ─── Sample data ──────────────────────────────────────────────────────────────

SAMPLE_SEQUENCE = [
    {
        "email": 1,
        "subject": "your SDR team expansion",
        "body": (
            "Saw you're hiring four SDRs simultaneously — that's a significant scale-up.\n\n"
            "The challenge most teams hit at this stage isn't recruiting — it's ramp time. "
            "When you're adding four people at once, the gaps in your onboarding process "
            "become very expensive very fast. The average ramp in your segment is around "
            "4.5 months; the fastest teams we've seen get it to 2.5.\n\n"
            "We've helped three similar-sized SaaS teams compress that gap. Happy to share "
            "what worked if it's useful.\n\n"
            "Worth 15 minutes to compare notes?"
        ),
    },
    {
        "email": 2,
        "subject": "re: your onboarding stack",
        "body": (
            "I hope this email finds you well. I wanted to follow up on my previous email.\n\n"
            "Just checking in to see if you had a chance to review what I sent. "
            "As mentioned, our platform offers a comprehensive suite of tools designed to "
            "help sales teams of all sizes achieve unprecedented growth through our "
            "revolutionary AI-powered onboarding solution.\n\n"
            "I'd love to schedule a 45-minute product demo at your earliest convenience. "
            "Please don't hesitate to reach out if you have any questions. "
            "I look forward to hearing from you!\n\n"
            "Click here to book a time: https://calendly.com/example"
        ),
    },
    {
        "email": 3,
        "subject": "SDR ramp benchmark",
        "body": (
            "One data point that might be useful: across the 40 SaaS teams we've benchmarked, "
            "the ones with the fastest SDR ramp time don't hire the most experienced reps — "
            "they invest more heavily in structured onboarding in the first 30 days.\n\n"
            "Happy to share the full breakdown. No catch — just thought it might be relevant "
            "given where you're headed.\n\n"
            "Useful?"
        ),
    },
    {
        "email": 4,
        "subject": "quick question",
        "body": (
            "Is SDR onboarding actually a priority right now, or is the timing just off?\n\n"
            "No judgment either way — just helps me know whether it's worth staying in touch."
        ),
    },
    {
        "email": 5,
        "subject": "last one",
        "body": (
            "I'll stop cluttering your inbox after this one.\n\n"
            "If scaling your SDR ramp time ever becomes a priority, happy to reconnect — "
            "just reply here.\n\n"
            "If there's someone else at your company who owns sales enablement, "
            "a name would go a long way.\n\n"
            "Either way, good luck with the expansion."
        ),
    },
]


# ─── Main ─────────────────────────────────────────────────────────────────────

def main():
    import argparse

    parser = argparse.ArgumentParser(
        description="Analyzes a cold email sequence for quality signals. "
                    "Evaluates word count, reading level, personalization, CTA clarity, "
                    "spam triggers, and subject lines. Scores each email 0-100."
    )
    parser.add_argument(
        "file", nargs="?", default=None,
        help="Path to a JSON file containing the email sequence. "
             "Use '-' to read from stdin. If omitted, runs embedded sample."
    )
    args = parser.parse_args()

    if args.file:
        if args.file == "-":
            sequence = json.load(sys.stdin)
        else:
            try:
                with open(args.file, "r", encoding="utf-8") as f:
                    sequence = json.load(f)
            except FileNotFoundError:
                print(f"Error: File not found: {args.file}", file=sys.stderr)
                sys.exit(1)
            except json.JSONDecodeError as e:
                print(f"Error: Invalid JSON: {e}", file=sys.stderr)
                sys.exit(1)
    else:
        print("No file provided — running on embedded sample sequence.\n")
        sequence = SAMPLE_SEQUENCE

    results = []
    for email in sequence:
        subject = email.get("subject", "")
        body = email.get("body", "")
        email_num = email.get("email", len(results) + 1)

        subject_analysis = analyze_subject_line(subject)
        body_analysis = analyze_body(body)

        # Overall score: 30% subject, 70% body
        overall = int(subject_analysis["score"] * 0.3 + body_analysis["score"] * 0.7)

        results.append({
            "email": email_num,
            "subject": subject,
            "subject_analysis": subject_analysis,
            "body_analysis": body_analysis,
            "overall_score": overall,
        })

    print_report(results)

    # JSON output for programmatic use
    summary = {
        "emails_analyzed": len(results),
        "average_score": sum(r["overall_score"] for r in results) // len(results) if results else 0,
        "results": [
            {
                "email": r["email"],
                "subject": r["subject"],
                "score": r["overall_score"],
                "word_count": r["body_analysis"]["word_count"],
                "has_strong_cta": r["body_analysis"]["has_strong_cta"],
                "spam_triggers": r["body_analysis"]["spam_triggers"],
                "subject_score": r["subject_analysis"]["score"],
            }
            for r in results
        ],
    }
    print("── JSON Output ──")
    print(json.dumps(summary, indent=2))


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)