Agency Skills
SkillsCollectionsContributorsContribute
Install every skill
Agency Skills

Skills written by the people who run agencies, consultancies and coaching firms. Free and open source, MIT.

LibraryAll skillsCollectionsContributorsInstall every skillFor AI agents (llms.txt)
ContributeHow to contributeContributor guideGitHubReport a problem
GiaCompany BrainBook a build session
Built by the community. Published by Gia.PrivacyTerms
Skills/Win the work/Sent cold email content review
Sales

Sent cold email content review

Use this skill when an agency needs to QA every cold email a client campaign has actually sent: names and company names rendering correctly, variables filled, message coherence, pitch accuracy, ICP fit, signal-to-message alignment, sender and signature consistency, CTA calibration and cross-campaign duplication. Produces findings tagged by severity and fix location with a prioritized action list. Also use for "review our emails", "QA our outbound", "are our emails going out correctly".

Garrett WolfeFounder, OneGTM
Say something like
“review our emails”“QA our outbound”“are our emails going out correctly”
instantlyclaycold-emailqaoutbounddeliverabilitydata-quality
SKILL.mdView on GitHub

Email Content Reviewer

A thorough, line-by-line quality review of every cold outbound email sent from an Instantly instance. This skill covers both programmatic checks (scripts that run across all emails) and qualitative LLM review (reading each email as a human would).

What this skill produces:

  • Issues tagged by severity: CRITICAL (broken/wrong), NOTABLE (suboptimal), PATTERN (systemic)
  • Every finding tagged with fix location: Instantly template, copy generation, or upstream data/enrichment
  • A prioritized action list

Script paths: references/: use the base directory shown in the system message when this skill loads.


Step 0: Identify the Right MCP Instance

If multiple Instantly MCPs are connected, identify the right one before pulling data:

  • Call list_campaigns (limit 5) on each MCP
  • Look at campaign names and sending addresses to match the client the user named
  • Use that MCP exclusively for the rest of the review

Step 1: Pull All Sent Email History

This requires pagination. Do not stop at one page.

1. Call list_emails(email_type=sent, limit=100, sort_order=asc)
2. Save the result file path from the token-limit error
3. Extract next_starting_after cursor from the file
4. Pull next page using that cursor
5. Repeat until next_starting_after is null

Each page will be saved as a separate file. Collect all file paths.

Also pull by campaign for any campaign where chronological pagination may have gaps:

  • Call list_emails(email_type=sent, limit=100, campaign_id=<id>) for each active campaign
  • This catches any emails that may have been skipped in chronological ordering

After pulling, merge all files into a single deduped corpus:

import json, glob
from html.parser import HTMLParser

class Strip(HTMLParser):
    def __init__(self): super().__init__(); self.r = []
    def handle_data(self, d): self.r.append(d)
    def text(self): return ' '.join(self.r).strip()

def strip(html):
    s = Strip(); s.feed(html or ''); return s.text()

files = glob.glob('<tool_results_dir>/mcp-<instance_id>-list_emails-*.txt')
all_emails = []; seen = set()
for fname in files:
    raw = json.load(open(fname))
    inner = json.loads(raw['result'])
    for e in inner.get('items', []):
        if e.get('id') not in seen:
            seen.add(e['id'])
            body_obj = e.get('body') or {}
            html = body_obj.get('html','') if isinstance(body_obj, dict) else str(body_obj)
            e['_body_text'] = strip(html)
            all_emails.append(e)

print(f"Total unique emails: {len(all_emails)}")
json.dump(all_emails, open('/tmp/all_emails.json','w'))

Coverage check: Compare total emails loaded against emails_sent_count from get_campaign_analytics. If coverage is below 85%, pull additional pages from specific date ranges or campaigns where you have gaps.


Step 2: Programmatic Analysis (Run Both Scripts)

# Script 1: Copy quality: variables, subjects, targeting
python3 references/audit_emails.py /tmp/all_emails_as_mcp_file.txt

# Script 2: Structural analysis: duplicates, steps, CTAs, verticals, competitive
python3 references/deep_analysis.py <tool_results_dir> <mcp_instance_id_prefix>

The scripts output findings directly. Note every flagged item: these become entries in the final report.


Step 3: LLM Content Review in Chunks

The programmatic scripts catch structural issues. This step catches meaning-level problems that require reading comprehension.

Split the emails into chunks of ~300 and review each chunk. For each email read:

3a. Name Quality

Does the first name used in the greeting look like a real, properly capitalized human name?

Flag:

  • All-lowercase or all-uppercase (e.g., "DAVID", "michael")
  • Initials used as first name ("Ck", "Jc", "Tj", "Lj")
  • Middle initial leaked into greeting ("Hey Austin X,", "Hi Raymond T,")
  • Full name instead of first name ("Hi John Smith,")
  • Last name used as first name: check if the email username format is lastname.firstinitial@ or similar
  • Numeric characters in name ("John2", "3Smith")
  • Name clearly doesn't match the recipient's email address in an obvious way (e.g., "Hi Sarah" going to john.smith@company.com when no nickname is plausible)

3b. Company Name Quality

Is the company name referenced in the email accurate and formatted correctly?

Flag:

  • Legal entity suffixes left in: "Acme Fluid Systems Pty Ltd", "Northwind Electric Mfg. LLC"
  • Initialisms lowercased: "Ibm" for IBM, "Amd" for AMD
  • Parent holding company used instead of operating brand: a holding group's name where the prospect knows only the trading brand
  • Wrong company entirely: the company name doesn't match the email domain in any recognizable way
  • LinkedIn artifacts: year founded, location code, or division appended ("Acme 1898 QC-CA", "Northwind Marketing Group NMG")
  • Two brands concatenated: "Acme Northwind"

3c. ICP Fit

Does this person's company actually need what the product does?

State who the product is for, then list who it is not for. Example, for a product built for teams with field sales reps who visit customer sites, flag:

  • Pure software/SaaS companies (no physical territory reps)
  • Financial services (inside sales or institutional, not field)
  • Educational institutions or nonprofits
  • Staffing and consulting firms
  • E-commerce / DTC companies

Write this list per client from their product description before reviewing.

3d. Signal-to-Message Alignment

If the campaign targets a specific signal (new hire, promotion, job posting, product launch, M&A), does the email opening correctly reference that signal for this specific recipient?

Flag:

  • Opening claims a signal that doesn't plausibly apply to this company (e.g., "I saw your firm just went through an M&A" for a small specialty manufacturer with no recent M&A activity)
  • The signal is real but the wrong person is being contacted (e.g., a promotion signal triggered outreach to the wrong person who didn't get promoted)
  • The opening references an event that happened to a parent company and is being attributed to a subsidiary

3e. Message Coherence

Read the email as the recipient would. Does it make sense from start to finish?

Flag:

  • The opening hook does not connect to the pitch ("Saw you're growing the team" → completely generic pitch that ignores the growth context)
  • Non-sequitur sentences: a sentence with no logical connection to the one before it
  • The same social proof example appears in step 1, step 2, and step 3 with no variation (the same customer story in every step with no new angle)
  • Broken causality: "Because you're scaling, our platform helps with admin": the causal connection is asserted but not explained
  • Bump follow-ups with zero added content ("Bumping this up." alone)
  • The CTA doesn't match the premise (email about scaling headcount → CTA is "worth 15 minutes to see if we can help with CRM data?": the ask doesn't connect to the headcount theme)

3f. Product Description Accuracy

Is the product described correctly? Check against what you know about the actual product.

List the product's core facts and cleared proof points, then flag:

  • Pre-launch language ("we're building X") when the product is live with customers
  • Stats not in the cleared proof point set (verify or remove)
  • A customer's name written incorrectly
  • A vague description ("AI platform") with no specific mechanism
  • ROI math applied to a company where its assumptions are clearly unrealistic (for example, 100 reps at a 10-person company)

Build this list per client from their product documentation before reviewing.

3g. Sender/Signature Consistency

Does the email's from address match the person signed at the bottom?

Flag: Any email where the sender's email domain/username clearly belongs to Person A but the closing signature says "Person B, Title, Company."

Note: When checking this, be careful not to confuse the recipient's first name (which may appear in the greeting) with a signature name. Only flag when the closing signature is clearly a different person from the sending account.


Step 4: Cross-Reference and Synthesize

After all chunks are reviewed:

  1. Deduplicate findings: the same broken template variant will appear on many emails. Report it once with a count ("affects N emails across campaign X").

  2. Assign fix locations:

    • Instantly: template configuration errors, empty step variants, step settings
    • Copy generation: outdated language, wrong product descriptions, unverified stats, copy that was generated incorrectly
    • Upstream data/enrichment (Clay etc.): wrong company names, ICP mismatches, name formatting errors, wrong company matched to contact
  3. Prioritize by impact:

    • CRITICAL: Broken emails landing in inboxes (empty body, wrong company name for a competitor, completely wrong recipient)
    • NOTABLE: Suboptimal but not harmful (bumps with no content, slightly off company name)
    • PATTERN: Systemic issue affecting many emails (pre-launch language, ROI math not scaled)

Step 5: Report Format

# [Client] Email Content Review
Date: [today]
Emails reviewed: [N] (covering [date range])
Coverage: [N]% of all-time sends

## Critical Issues
[Numbered. For each: what's wrong, recipient example, exact quote, count affected]

## Issues by Category

### Names
[List each issue type with count and examples]

### Company Names
[List each issue type with count and examples]

### ICP Fit
[Which verticals/titles are out of scope, with counts]

### Copy Coherence & Pitch Accuracy
[Specific examples of broken sentences, wrong stats, pre-launch language]

### Structural (Steps, CTAs, Sender Consistency)
[From script output: step distribution, CTA breakdown, duplicates]

## Fix Location Summary

### Fix in Instantly
[Specific campaigns, step numbers, what to change]

### Fix in Copy Generation
[Template text to update, stats to verify or remove]

### Fix in Upstream Data (Clay / enrichment)
[Filters to add, normalization rules, ICP criteria]

## Priority Action List
### Do immediately
### Do this week
### Monitor going forward

What good looks like

  • Coverage is at least 85% of all-time sends before drawing conclusions, and small samples (under 50 sends) are labeled as such.
  • The same broken template is reported once, with a count of the emails it affects.
  • Every finding carries a severity and a fix location: the sending tool, copy generation, or upstream data.
  • Red flag: an email that passes every structural check but does not make sense read start to finish. Read each one as the recipient would.

Notes on Scope

  • This skill reviews sent emails: what actually went out to prospects. It will not catch issues in draft campaigns or sequences that haven't fired yet.
  • For campaigns that just launched (<50 emails sent), note the sample size and flag that findings may not be representative.
  • The LLM review step requires actual judgment: reading each email as a thoughtful human would. Do not rush it. An email that looks fine structurally can still have a logical disconnect that only reveals itself when read naturally.
  • When in doubt about whether a company name issue is from the wrong source or just a legitimate alternate name, check the email domain against the company name. If they don't obviously match, flag it.
2 reference files
references/audit_emails.py

#!/usr/bin/env python3 """ Instantly Campaign Email Auditor Analyzes a batch of sent emails for variable rendering issues, copy quality problems, and targeting anomalies. Takes the path to the saved MCP tool result file as input.

Usage: python3 audit_emails.py <path_to_emails_file> python3 audit_emails.py <path_to_emails_file> --limit 200

Output: Structured findings printed to stdout, grouped by issue type. """

import json import re import sys import argparse from html.parser import HTMLParser from collections import Counter, defaultdict

─── HTML stripping ──────────────────────────────────────────────────────────

class HTMLStripper(HTMLParser): def init(self): super().init() self._text = []

def handle_data(self, d):
    self._text.append(d)

def get_text(self):
    return ' '.join(self._text).strip()

def strip_html(html_str): if not html_str: return '' s = HTMLStripper() try: s.feed(html_str) return s.get_text() except Exception: return html_str

def check_html_in_plain_text(plain_text): """ Detect HTML tags that leaked into the RENDERED plain text: meaning the recipient would literally see '<div>' or '<br>' in their inbox. This is different from an HTML email having HTML in its source (which is fine). """ # Only flag if the stripped plain text still contains literal tag syntax leaking = re.findall(r'<(?:div|br|p|span|b|i|u|a|ul|li|h[1-6]|strong|em)\b', plain_text, re.IGNORECASE) return leaking

─── Variable detection patterns ─────────────────────────────────────────────

Templates still present (never rendered)

UNFILLED_PATTERNS = [ re.compile(r'{{(?!RANDOM|accountSignature)[^}]{1,60}}}'), # {{variable}} re.compile(r'[(?:FIRST[ _]?NAME|LAST[ _]?NAME|COMPANY(?:[ ]?NAME)?|TITLE|EMAIL|PHONE|LINKEDIN)]', re.IGNORECASE), re.compile(r'%[A-Z]{2,30}%'), # %VARIABLE% re.compile(r'[[.{1,30}]]'), # [[variable]] style ]

Soft failure: variable rendered to an empty or near-empty value

EMPTY_GREETING_PATTERNS = [ re.compile(r'(?:Hi|Hey|Hello|Dear)\s*,'), # "Hi ,": name was blank re.compile(r'(?:Hi|Hey|Hello|Dear)\s+.\s'), # "Hi . " re.compile(r'(?:Hi|Hey|Hello|Dear)\s+undefined\b', re.IGNORECASE), re.compile(r'(?:Hi|Hey|Hello|Dear)\s+null\b', re.IGNORECASE), re.compile(r'(?:Hi|Hey|Hello|Dear)\s+N/A\b', re.IGNORECASE), re.compile(r'(?:Hi|Hey|Hello|Dear)\s+{{'), # template not stripped ]

Only flag double-spaces that indicate a blank variable was dropped in, not

normal phrases like "the portco" or "the team"

EMPTY_COMPANY_PATTERNS = [ re.compile(r'\bat\s{2,}(?:\w)'), # "at company" (two+ spaces then a word) re.compile(r'\bfor\s{2,}(?:\w)'), # "for company" re.compile(r"^'s\b"), # starts with 's (company name was blank) re.compile(r'(?:at|for|of|with)\s+'s\b'), # "at 's team": blank name before apostrophe-s ]

Generic/catch-all email prefixes that are almost never real decision-maker inboxes

GENERIC_EMAIL_PREFIXES = { 'info', 'admin', 'contact', 'hello', 'support', 'noreply', 'no-reply', 'help', 'sales', 'marketing', 'hr', 'team', 'office', 'careers', 'jobs', 'webmaster', 'postmaster', 'mail', 'enquiries', 'enquiry', 'hello', 'hi', 'service', 'billing', 'accounts', }

─── Copy quality checks ─────────────────────────────────────────────────────

def check_empty_or_short(body_text): """ Flag emails with suspiciously little content. Note: very short 'bump' emails (e.g. 'Bumping this up.') are common follow-up patterns: they're flagged here because they add no value, but that's a copywriting decision to surface rather than an outright bug. """ word_count = len(body_text.split()) if word_count == 0: return 'EMPTY body: no content at all' if word_count < 8: return f'NEAR-EMPTY body ({word_count} words): likely a bump step with no real value: "{body_text[:120]}"' return None

def check_subject_issues(subject, step): """Flag missing subjects on step 1 emails.""" clean = (subject or '').strip() step_str = str(step or '') # Step identifiers for the first touch vary: check for common patterns is_first_step = ( step_str in ('0_0_0', '0_0_1', '0_0_2', '1', 'step_1', '1_1') or re.match(r'^0_0_\d$', step_str) is not None ) if not clean and is_first_step: return 'EMPTY subject on first-touch email: prospects have no reason to open this' if len(clean) < 3 and clean: return f'EXTREMELY SHORT subject: "{clean}"' return None

def check_truncation(body_text): """Detect copy that appears to trail off mid-sentence.""" stripped = body_text.strip() if len(stripped) > 80: last_60 = stripped[-60:] # No sentence-ending punctuation in last 60 chars, ends mid-word if not re.search(r'[.!?]\s*$', stripped) and re.search(r'[a-z]{3,}$', stripped): # Exclude known short-form endings like CTAs cta_endings = ['let me know', 'open to chat', 'worth a look', 'happy to share', 'door is open', "door's open", 'reach out', 'pick this up'] if any(stripped.lower().endswith(e) for e in cta_endings): return None # Exclude email signatures: after HTML stripping, signatures appear as plain text # and typically end with a company name or title (capitalized, no punctuation needed) sig_patterns = [ re.compile(r'\b(?:Co-Founder|Founder|CEO|CTO|VP|President|Director)\s+[A-Z][a-z]+\s*$'), re.compile(r'Reply\s+"pause"', re.IGNORECASE), # Generic: last word is an all-caps abbreviation or TitleCase company name after a title re.compile(r'(?:Founder|CEO|President|Director)\s+[A-Z][A-Za-z]+(?:\s+[A-Z][A-Za-z]+)?\s*$'), ] if any(p.search(stripped) for p in sig_patterns): return None return f'POSSIBLE TRUNCATION: ends without punctuation: "...{last_60}"' return None

def check_name_mismatch(body_text, to_address): """ Check if the name used in the greeting matches the recipient's email address. E.g., greeting says 'Hey Sarah' but email is john.smith@domain.com """ if not to_address or '@' not in to_address: return None

# Extract name from greeting (first word after Hi/Hey/Hello)
greeting_match = re.match(r'(?:Hi|Hey|Hello|Dear)\s+([A-Z][a-z]{1,20})', body_text)
if not greeting_match:
    return None
greeting_name = greeting_match.group(1).lower()

# Extract name components from email address
email_prefix = to_address.split('@')[0].lower()
# Normalize separators
email_parts = re.split(r'[._\-]', email_prefix)
email_parts = [p for p in email_parts if len(p) > 1]

# If greeting name doesn't appear in any email name component, flag it
if email_parts and not any(greeting_name in part or part in greeting_name for part in email_parts):
    # Only flag if the email looks like a personal address (first.last format), not a generic
    if len(email_parts) >= 2:
        return f'NAME MISMATCH: greeting says "{greeting_name}" but email is "{to_address}"'
return None

def check_targeting_coherence(body_text, to_address, subject): """ Flag obvious targeting mismatches where the email's premise doesn't fit the recipient. These are heuristic patterns: the LLM review pass catches more nuanced cases. """ flags = []

# Email talks about "portfolio companies" / "portcos" going to a non-PE domain
# Common non-PE domains that end up in PE campaigns
NON_PE_SIGNALS = ['morganstanley', 'goldmansachs', 'jpmorgan', 'bofa', 'citigroup',
                  'blackrock', 'fidelity', 'vanguard', 'mastercard', 'visa', 'amex']
portco_mentions = re.search(r'\b(?:portco|portfolio compan|portfolio-level)\b', body_text, re.IGNORECASE)
if portco_mentions and to_address:
    domain = to_address.split('@')[-1].lower() if '@' in to_address else ''
    for signal in NON_PE_SIGNALS:
        if signal in domain:
            flags.append(
                f'TARGETING MISMATCH: email refers to "portcos/portfolio companies" but recipient is at {domain} (likely not a PE firm with software portcos)'
            )
            break

# Email mentions "your firm" in a PE/fund context going to a corporate
firm_context = re.search(r'\byour firm\b.{0,60}(?:portco|fund|investment|portfolio)', body_text, re.IGNORECASE)
if firm_context and to_address:
    domain = to_address.split('@')[-1].lower() if '@' in to_address else ''
    for signal in NON_PE_SIGNALS:
        if signal in domain:
            flags.append(f'POSSIBLE TARGETING MISMATCH: "your firm" + PE context sent to {domain}')
            break

return flags

def check_nonsensical_connections(body_text): """ Flag sentences where the causal or logical connection seems broken. These are heuristic catches: a human should verify each one. """ flags = []

# "That signals X" where the antecedent is about a hiring action sent to a non-PE company
that_signals = re.findall(r'That signals[^.]{10,100}(?:portfolio|portco)', body_text, re.IGNORECASE)
for m in that_signals[:2]:
    flags.append(f'CHECK LOGIC: "That signals...portfolio" may not apply to this recipient: "{m.strip()[:100]}"')

return flags

def is_generic_email(to_address): if not to_address or '@' not in to_address: return False prefix = to_address.split('@')[0].lower().strip() return prefix in GENERIC_EMAIL_PREFIXES

def check_opening_hook_repetition(first_sentence, seen_hooks, ctx): """ Flag when the opening personalization line (the hook) is identical across emails. Template body copy WILL be the same: that's fine. But the hook should vary. """ if len(first_sentence) < 20: return None normalized = re.sub(r'\s+', ' ', first_sentence.lower().strip()) # Remove names/company names before comparing (so "Saw X hiring" == "Saw Y hiring") generic = re.sub(r'\b[A-Z][a-z]+(?:\s[A-Z][a-z]+)?\b', 'NAME', first_sentence) generic_n = re.sub(r'\s+', ' ', generic.lower().strip()) if generic_n in seen_hooks and len(generic_n) > 40: return f'HOOK NOT UNIQUE: same opening premise as another email in this batch: "{first_sentence[:80]}"' seen_hooks.add(generic_n) return None

─── Main analysis ────────────────────────────────────────────────────────────

def analyze_emails(emails): findings = defaultdict(list) seen_hooks = set() domain_counter = Counter() sender_domain_counter = Counter() step_counter = Counter()

for i, e in enumerate(emails):
    if not isinstance(e, dict):
        continue

    email_id = e.get('id', f'email_{i}')
    to = e.get('to_address_email_list') or e.get('to_address') or ''
    frm = e.get('from_address_email') or e.get('eaccount') or ''
    subj = (e.get('subject') or '').strip()
    step = str(e.get('step', ''))
    ts = (e.get('timestamp_email') or e.get('timestamp_created') or '')[:10]

    body_obj = e.get('body') or {}
    raw_html = body_obj.get('html', '') if isinstance(body_obj, dict) else str(body_obj)
    body_text = strip_html(raw_html)

    step_counter[step] += 1

    ctx = f'[{ts}] step={step} | {frm} -> {to} | subj="{subj[:60]}"'

    # Track domains
    if '@' in to:
        domain_counter[to.split('@')[1]] += 1
    if '@' in frm:
        sender_domain_counter[frm.split('@')[1]] += 1

    # ── Variable rendering ──
    for pat in UNFILLED_PATTERNS:
        hits = pat.findall(subj + ' ' + body_text)
        if hits:
            findings['unfilled_variables'].append({
                'ctx': ctx,
                'issue': f'Unfilled template variable(s) still in email: {hits[:5]}',
                'body_snippet': body_text[:200]
            })
            break

    for pat in EMPTY_GREETING_PATTERNS:
        m = pat.search(body_text)
        if m:
            findings['empty_name_greeting'].append({
                'ctx': ctx,
                'issue': f'Greeting rendered with empty/broken name',
                'match': m.group(0),
                'body_snippet': body_text[:150]
            })
            break

    for pat in EMPTY_COMPANY_PATTERNS:
        m = pat.search(body_text)
        if m:
            findings['empty_company_variable'].append({
                'ctx': ctx,
                'issue': 'Company name variable rendered blank',
                'match': repr(m.group(0)),
            })
            break

    # ── Body quality ──
    empty_flag = check_empty_or_short(body_text)
    if empty_flag:
        findings['empty_or_short_body'].append({'ctx': ctx, 'issue': empty_flag})

    subj_flag = check_subject_issues(subj, step)
    if subj_flag:
        findings['subject_issues'].append({'ctx': ctx, 'issue': subj_flag})

    trunc_flag = check_truncation(body_text)
    if trunc_flag:
        findings['truncation'].append({'ctx': ctx, 'issue': trunc_flag})

    # HTML leaking into PLAIN TEXT (not just "has HTML in the HTML body")
    leaking_tags = check_html_in_plain_text(body_text)
    if leaking_tags:
        findings['html_leaking'].append({
            'ctx': ctx,
            'issue': f'HTML tags visible as literal text in rendered email: {set(leaking_tags[:5])}',
            'body_snippet': body_text[:200]
        })

    # ── Name mismatch ──
    name_mismatch = check_name_mismatch(body_text, to)
    if name_mismatch:
        findings['name_mismatch'].append({
            'ctx': ctx,
            'issue': name_mismatch,
            'body_snippet': body_text[:100]
        })

    # ── Targeting ──
    targeting_flags = check_targeting_coherence(body_text, to, subj)
    for f in targeting_flags:
        findings['targeting_mismatch'].append({'ctx': ctx, 'issue': f, 'body_snippet': body_text[:200]})

    nonsense_flags = check_nonsensical_connections(body_text)
    for f in nonsense_flags:
        findings['copy_logic_issues'].append({'ctx': ctx, 'issue': f, 'body_snippet': body_text[:300]})

    # ── Hook uniqueness (only check first-touch emails) ──
    is_first_touch = re.match(r'^0_0_\d$', step) or step in ('1', 'step_1')
    if is_first_touch and body_text:
        sentences = [s.strip() for s in re.split(r'[.!?]', body_text) if len(s.strip()) > 20]
        if sentences:
            hook_flag = check_opening_hook_repetition(sentences[0], seen_hooks, ctx)
            if hook_flag:
                findings['non_unique_hook'].append({'ctx': ctx, 'issue': hook_flag})

    # ── Generic email address ──
    if is_generic_email(to):
        findings['generic_recipient_email'].append({
            'ctx': ctx,
            'issue': f'Sending to generic/role-based address: these almost never reply'
        })

return findings, domain_counter, sender_domain_counter, step_counter

def print_findings(findings, domain_counter, sender_domain_counter, step_counter, total_emails): print(f"\n{'='*70}") print(f"EMAIL AUDIT RESULTS: {total_emails} emails analyzed") print(f"{'='*70}\n")

severity_order = [
    ('unfilled_variables',       '🔴 CRITICAL: Unfilled Template Variables'),
    ('empty_name_greeting',      '🔴 CRITICAL: Empty Name in Greeting'),
    ('empty_or_short_body',      '🔴 CRITICAL: Empty or Near-Empty Email Body'),
    ('subject_issues',           '🟠 HIGH: Subject Line Issues'),
    ('targeting_mismatch',       '🟠 HIGH: Targeting Mismatch (Wrong ICP)'),
    ('name_mismatch',            '🟠 HIGH: Name in Greeting Doesn\'t Match Recipient'),
    ('html_leaking',             '🟠 HIGH: HTML Tags Visible as Literal Text'),
    ('empty_company_variable',   '🟠 HIGH: Company Name Variable Rendered Blank'),
    ('truncation',               '🟡 MEDIUM: Possible Truncated Copy'),
    ('copy_logic_issues',        '🟡 MEDIUM: Copy Logic to Review'),
    ('non_unique_hook',          '🟡 MEDIUM: Opening Hook Not Unique Across Emails'),
    ('generic_recipient_email',  '🟡 MEDIUM: Generic Recipient Email Addresses'),
]

total_issues = sum(len(v) for v in findings.values())
if total_issues == 0:
    print("✅ No programmatic issues detected. Proceed to manual copy review.\n")
else:
    issue_types = len([k for k, v in findings.items() if v])
    print(f"Found {total_issues} flagged instances across {issue_types} issue type(s).\n")

for key, label in severity_order:
    items = findings.get(key, [])
    if not items:
        continue
    print(f"\n{label} ({len(items)} instance{'s' if len(items)>1 else ''})")
    print('-' * 60)
    for item in items[:8]:
        print(f"  {item['ctx']}")
        print(f"  Issue: {item['issue']}")
        if 'match' in item:
            print(f"  Match: {item['match']}")
        if 'body_snippet' in item:
            snippet = item['body_snippet'][:200].replace('\n', ' ')
            print(f"  Body: {snippet}")
        print()
    if len(items) > 8:
        print(f"  ... and {len(items)-8} more\n")

# Step distribution
print(f"\n{'='*70}")
print("STEP DISTRIBUTION")
print('-' * 60)
for step, cnt in sorted(step_counter.items(), key=lambda x: -x[1]):
    print(f"  step {step}: {cnt} emails")

# Recipient domain breakdown
print(f"\n{'='*70}")
print("RECIPIENT DOMAIN BREAKDOWN (top 20)")
print('-' * 60)
for domain, cnt in domain_counter.most_common(20):
    print(f"  {domain}: {cnt} emails")

# Flag if one domain dominates (potential targeting concentration risk)
if domain_counter:
    top_domain, top_count = domain_counter.most_common(1)[0]
    pct = top_count / total_emails * 100
    if pct > 25:
        print(f"\n  ⚠️  CONCENTRATION WARNING: {top_domain} accounts for {pct:.0f}% of sends in this sample")

print(f"\n{'='*70}")
print("SENDING DOMAIN BREAKDOWN (top 15)")
print('-' * 60)
for domain, cnt in sender_domain_counter.most_common(15):
    print(f"  {domain}: {cnt} emails")

def main(): parser = argparse.ArgumentParser(description='Audit Instantly sent emails for issues.') parser.add_argument('file', help='Path to the saved MCP tool result JSON file') parser.add_argument('--limit', type=int, default=None, help='Max emails to analyze') args = parser.parse_args()

with open(args.file) as f:
    raw = json.load(f)

# Handle the nested structure from the MCP file format: {result: "<json string>"}
if isinstance(raw, dict) and 'result' in raw:
    inner = json.loads(raw['result'])
else:
    inner = raw

if isinstance(inner, dict):
    emails = inner.get('items', [])
elif isinstance(inner, list):
    emails = inner
else:
    print(f"Unexpected data structure: {type(inner)}")
    sys.exit(1)

if args.limit:
    emails = emails[:args.limit]

findings, domain_counter, sender_domain_counter, step_counter = analyze_emails(emails)
print_findings(findings, domain_counter, sender_domain_counter, step_counter, len(emails))

if name == 'main': main()

references/deep_analysis.py

#!/usr/bin/env python3 """ Deep structural analysis of all sent emails from an Instantly instance. Covers: cross-campaign duplicates, subject line quality, step/sequence structure, sender/signature consistency, CTA calibration, competitive mentions, vertical distribution.

Usage: python3 deep_analysis.py <emails_dir> # emails_dir should contain all the saved list_emails MCP result files

Output: Structured findings to stdout. """

import json, glob, re, sys, os from html.parser import HTMLParser from collections import Counter, defaultdict

class Strip(HTMLParser): def init(self): super().init(); self.r = [] def handle_data(self, d): self.r.append(d) def text(self): return ' '.join(self.r).strip()

def strip(html): s = Strip() try: s.feed(html or ''); return s.text() except: return html or ''

def load_emails(emails_dir, mcp_prefix): """Load and deduplicate all emails from saved MCP result files.""" pattern = os.path.join(emails_dir, f'{mcp_prefix}-list_emails-*.txt') files = sorted(glob.glob(pattern)) all_emails = []; seen = set() for fname in files: with open(fname) as f: raw = json.load(f) inner = json.loads(raw['result']) for e in inner.get('items', []): eid = e.get('id','') if eid not in seen: seen.add(eid) body_obj = e.get('body') or {} html = body_obj.get('html','') if isinstance(body_obj, dict) else str(body_obj) e['_body_text'] = strip(html) all_emails.append(e) return all_emails

def get_persona(from_addr): # Groups results by sending mailbox. Map usernames to sender names here if you want them merged. return (from_addr.split('@')[0] if '@' in from_addr else from_addr).lower()

Set these per client. Each keyword is matched against the recipient's email address.

VERTICAL_KEYWORDS = { 'tech/software': ['.ai', '.io', 'saas', 'software'], 'education/nonprofit': ['.edu', '.k12', 'university', 'college', 'school'], 'pharma/biotech/medtech': ['pharma', 'biotech', 'medtech'], 'food/beverage/CPG': ['food', 'beverage'], }

def classify_vertical(email): d = email.lower() if email else '' for vertical, keywords in VERTICAL_KEYWORDS.items(): if any(k in d for k in keywords): return vertical return 'other'

def analyze(all_emails): total = len(all_emails) print(f"\n{'='*70}") print(f"DEEP ANALYSIS: {total} emails") print(f"{'='*70}")

# Date range
dates = sorted(set(e.get('timestamp_email','')[:10] for e in all_emails if e.get('timestamp_email')))
if dates: print(f"Date range: {dates[0]} to {dates[-1]}")

# 1. Cross-campaign duplicates
print(f"\n{'─'*60}")
print("1. CROSS-CAMPAIGN RECIPIENT OVERLAP")
print(f"{'─'*60}")
rc = defaultdict(set)
for e in all_emails:
    to = (e.get('to_address_email_list') or '').lower().strip()
    c = e.get('campaign_id','')[:8]
    if to: rc[to].add(c)
multi = {r: camps for r, camps in rc.items() if len(camps) > 1}
print(f"  Recipients in 2+ campaigns: {len(multi)}")
if multi:
    print("  Examples:")
    for r, camps in sorted(multi.items())[:15]:
        print(f"    {r}: {sorted(camps)}")
else:
    print("  ✅ No recipients are being double-contacted across campaigns.")

# 2. Subject line analysis
print(f"\n{'─'*60}")
print("2. SUBJECT LINE QUALITY")
print(f"{'─'*60}")
no_subj = [e for e in all_emails if not (e.get('subject') or '').strip()]
empty_by_step = Counter(e.get('step','') for e in no_subj)
print(f"  Empty subjects: {len(no_subj)} ({len(no_subj)/total*100:.0f}%)")
print(f"    By step: {dict(empty_by_step.most_common(8))}")

fake_re = [e for e in all_emails
           if (e.get('subject') or '').startswith('Re:')
           and str(e.get('step','')).startswith('0_0_')]
if fake_re:
    print(f"  ⚠️  'Re:' on first-touch steps: {len(fake_re)}")
    for e in fake_re[:5]:
        print(f"    {e.get('to_address_email_list','')} | '{e.get('subject','')}'")
else:
    print(f"  ✅ No 'Re:' subjects on first-touch emails.")

top_subj = Counter((e.get('subject') or '').strip() for e in all_emails if (e.get('subject') or '').strip())
print(f"\n  Top 12 subjects by frequency:")
for subj, cnt in top_subj.most_common(12):
    print(f"    {cnt}x: '{subj[:72]}'")

# 3. Step quality + length
print(f"\n{'─'*60}")
print("3. SEQUENCE STEP QUALITY")
print(f"{'─'*60}")
step_dist = Counter(e.get('step','') for e in all_emails)
print("  Step distribution:")
for step, cnt in sorted(step_dist.items()):
    print(f"    {step}: {cnt}")

bump_pat = re.compile(r'^(hi |hey |dear )?[a-z ]+?, ?(bumping|resurfacing|following up|circling back)', re.I)
bumps = [e for e in all_emails if bump_pat.match((e.get('_body_text') or '')[:100])]
print(f"\n  Empty bump emails (bumping/resurfacing with no value add): {len(bumps)}")
for e in bumps[:5]:
    print(f"    {e.get('to_address_email_list','')} | step={e.get('step','')} | '{e.get('_body_text','')[:80]}'")

length_by_step = defaultdict(list)
for e in all_emails:
    lvl = str(e.get('step',''))[:3]
    wc = len((e.get('_body_text') or '').split())
    length_by_step[lvl].append(wc)

print("\n  Avg word count by step level:")
for lvl in sorted(length_by_step):
    ws = length_by_step[lvl]
    if ws: print(f"    {lvl}_x: avg={sum(ws)/len(ws):.0f}w, min={min(ws)}, max={max(ws)}, n={len(ws)}")

over200 = [e for e in all_emails if len((e.get('_body_text') or '').split()) > 200]
if over200:
    print(f"\n  ⚠️  Emails >200 words (too long for cold outreach): {len(over200)}")
    for e in over200[:5]:
        wc = len((e.get('_body_text') or '').split())
        print(f"    {e.get('to_address_email_list','')} | {wc}w | step={e.get('step','')}")
else:
    print(f"\n  ✅ All emails under 200 words: appropriate length.")

# 4. Sender persona analysis
print(f"\n{'─'*60}")
print("4. SENDER PERSONA DISTRIBUTION")
print(f"{'─'*60}")
persona_counts = Counter(get_persona(e.get('from_address_email','')) for e in all_emails)
for p, cnt in persona_counts.most_common():
    print(f"  {p}: {cnt} emails ({cnt/total*100:.0f}%)")

# 5. CTA calibration
print(f"\n{'─'*60}")
print("5. CALL-TO-ACTION DISTRIBUTION")
print(f"{'─'*60}")
cta_pats = {
    '15 minutes': re.compile(r'worth 15 min|15 minutes|15 mins', re.I),
    '20 minutes': re.compile(r'worth 20 min|20 minutes|20 mins', re.I),
    'quick call/chat': re.compile(r'quick call|quick chat', re.I),
    'worth a look': re.compile(r'worth a look', re.I),
    'open to': re.compile(r'\bopen to\b', re.I),
    'happy to show/share': re.compile(r'happy to show|happy to share', re.I),
}
cta_counts = Counter()
for e in all_emails:
    body = e.get('_body_text','')
    matched = next((label for label, pat in cta_pats.items() if pat.search(body)), 'no clear CTA')
    cta_counts[matched] += 1
for cta, cnt in cta_counts.most_common():
    flag = ' ⚠️' if cta == 'no clear CTA' and cnt/total > 0.3 else ''
    print(f"  '{cta}': {cnt} ({cnt/total*100:.0f}%){flag}")

# 6. Competitive mentions
print(f"\n{'─'*60}")
print("6. COMPETITIVE / THIRD-PARTY MENTIONS")
print(f"{'─'*60}")
# Set these per client: the CRMs you integrate with (mark 'intentional') and named competitors.
watch_terms = {
    'salesforce': 'CRM mentioned: intentional',
    'hubspot': 'CRM mentioned: intentional',
    'copilot': 'Ambiguous: could be confused with Microsoft Copilot',
    '<competitor name>': 'Direct competitor',
}
for term, note in watch_terms.items():
    hits = [e for e in all_emails if term in (e.get('_body_text','') or '').lower()]
    if hits:
        flag = ' ✅' if 'intentional' in note else ' ⚠️' if 'competitor' in note or 'Ambiguous' in note else ' ℹ️'
        print(f"  '{term}': {len(hits)} mentions{flag}: {note}")
        for e in hits[:2]:
            idx = e.get('_body_text','').lower().find(term)
            snip = e.get('_body_text','')[max(0,idx-30):idx+60].replace('\n',' ')
            print(f"    -> {e.get('to_address_email_list','')} | '...{snip}...'")

# 7. Vertical distribution
print(f"\n{'─'*60}")
print("7. RECIPIENT COMPANY VERTICAL DISTRIBUTION")
print(f"{'─'*60}")
vert_dist = Counter()
for e in all_emails:
    vert_dist[classify_vertical(e.get('to_address_email_list',''))] += 1
for vert, cnt in vert_dist.most_common():
    flag = ''  # set per client, e.g. ' NOT CORE ICP' for verticals outside the ICP
    print(f"  {vert}: {cnt} ({cnt/total*100:.0f}%){flag}")

if name == 'main': if len(sys.argv) < 3: print("Usage: deep_analysis.py <emails_dir> <mcp_prefix>") print(" emails_dir: directory containing saved MCP result files") print(" mcp_prefix: e.g. 'mcp-<instance-id>'") sys.exit(1) emails_dir = sys.argv[1] mcp_prefix = sys.argv[2] emails = load_emails(emails_dir, mcp_prefix) print(f"Loaded {len(emails)} unique emails from {emails_dir}") analyze(emails)

Install this skill

Pick the app you use. We remember it for next time.

  1. Download the skill as a ZIP.
  2. In Claude, open Customize, then Skills, then Your skills.
  3. Choose Upload skill and pick the ZIP. Leave it switched on.
Download ZIPOpen Claude skills

Then ask: “review our emails”

Want the whole library, kept up to date? .

Run it on your own playbookBook 30 minutes. We build it onto a Company Brain live.
Win the workcollection
Salescategory
MITlicense

Install this skill

Pick the app you use. We remember it for next time.

  1. Download the skill as a ZIP.
  2. In Claude, open Customize, then Skills, then Your skills.
  3. Choose Upload skill and pick the ZIP. Leave it switched on.
Download ZIPOpen Claude skills

Then ask: “review our emails”

Want the whole library, kept up to date? .

Want this sounding like you?

Out of the box it runs on general knowledge. On a Company Brain it runs on your framework, your pricing and the proposals you’ve already won with, so the output comes back in your voice.

Book 30 minutes

More from Garrett

Customer Success

Client performance review doc

Use this skill when an agency owes a client its periodic outbound performance review (QBR, 30-60-90, "three months in") and wants a commentable doc the client reads before the call. Pulls the numbers by channel and campaign, writes the summary, levers, what has not worked, new plays and the client's asks, adds a data appendix (TAM, who replies, objections), renders clean tables and charts, and runs the comment-and-call loop. Also use for "performance review", "QBR", "results doc", "write up how outbound is going".

Garrett WolfeFounder, OneGTM
Sales

Cold email sequence that gets positive replies

Use this skill when an agency is writing, rewriting or reviewing cold email for a client: a full sequence, a subject line, first line, P.S., CTA, offer, follow-up step, spintax, a signal or trigger campaign, conference outreach, a segment and offer map, or an A/B test. Walks the decisions in order (value lever, list and signal, offer, subject, first line, body framework, CTA, follow-ups), lints the copy and sizes or reads tests. Also use for "build a sequence", "why isn't this campaign getting replies", "did variant B win".

Garrett WolfeFounder, OneGTM
RevOps

TAM scoring audit and distribution fix

Use this skill when a client's account scoring model piles everyone at the same score and the agency needs to audit it against the data, simulate revised rubrics and recommend bands, weights and multipliers that make accounts stack-rank, with a DQ cutoff that means something. Works on a Clay or spreadsheet TAM export plus its rubric. Also use for "rescore this list", "fix the scoring", "why is everyone scoring the same", "distribution is compressed", "twin-firm test".

Garrett WolfeFounder, OneGTM

More in Win the work

Sales Coaching

Activity incentive sprint

Use this skill when a sales team's pattern needs interrupting for a few days: Call 1 bookings have dried up, presentations have stalled, a dormant list needs clearing, or the quarter needs a jolt. Produces an Executive Sprint Brief for a 1-day blitz, 3-day push or 2-week sprint, with targets worked from the team's real baseline, qualifying criteria, a scoreboard, a reward and three daily huddle prompts. Also use for "sales blitz", "sprint incentive", "spiff", "activity competition", "get the team booking meetings this week".

Tony DowlingTony DowlingDirector, CONVRG
Marketing

Buyer read

Use this skill when a finished page, deck, or piece of client copy is about to go to the client or be published, and a real ICP or buyer research document exists. Produces a ranked list of what would make the target buyer stop trusting it, read in character, with every finding citing a line in a real source, plus what to cut and a score. Catches what craft reviews miss: scenarios that can't happen in the buyer's week, an offer never plainly stated, missing price, unused proof, seller vocabulary. Also use for "/buyer-read", "review this as the buyer", "have the ICP look at this", "what would a CFO think of this page", "be hypercritical about this copy". Not for grammar or voice checks, and never for rewriting.

Chris DuBoisChris DuBoisFounder and Coach, Dynamic Agency OS
Sales

Client proposal

Use this skill when a proposal is due and it is about to be the last one with the name changed. Produces a proposal built from what the prospect actually said: their situation in their own words, the firm's named approach, scope with explicit exclusions, the objection they already raised answered directly, pricing options, and the proof the firm can stand behind. Also use for "I need a proposal out tonight", "respond to this RFP", "write the SOW", "pitch document".

Tapajyoti (Tukan) DasTapajyoti (Tukan) DasCEO, Gia