cold-email-outreach — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited cold-email-outreach (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
End-to-end cold outreach: research, draft, send, follow up, route replies. Strategy is grounded in proven hook frameworks (number-led / question / pain-point / benefit-first); the execution runs on Apollo, Firecrawl, the LinkedIn scraper, and Gmail through the Hyper MCP.
| Request | Send them to |
|---|---|
| Lifecycle / nurture sequences for warm leads (welcome, onboarding, re-engagement, win-back) | email-lifecycle (planned) |
| LinkedIn DMs, connection requests, or Sales Navigator workflows | (planned) |
| Lead scoring, routing, deal-stage updates after a reply | crm-revops (planned) |
| Scraping competitor ads | meta-ads-library |
If gmail_messages_send and apollo_people_search are not in the agent's tool list, stop and tell the user to enable the Hyper MCP and connect Gmail + Apollo.
Every tool in this skill is named by its canonical tool name. Run it with the call your surface gives you:
| Surface | Find a tool | Run it |
|---|---|---|
| MCP client (Claude, Cursor, Codex, ChatGPT) | search("<what you want to do>"), then describe("<name>") | call("<name>", {...}) |
| Hyper CLI | hyperai search "<what you want to do>", then hyperai describe <name> | hyperai call <name> --json '{...}' |
If a tool is not found, its integration is not connected or not enabled for the workspace: stop and tell the user which integration to connect.
| Phase | Tools |
|---|---|
| Prospect research | apollo_people_search, apollo_companies_search, apollo_people_match_bulk (preferred for 2+ enrich), apollo_people_match (single only) |
| Per-prospect signals | firecrawl_urls_scrape, firecrawl_urls_scrape_batch, firecrawl_branding_extract, firecrawl_screenshots_create, scrape_linkedin_profiles (conditional — requires LinkedIn Apify integration) |
| Drafting | gmail_drafts_create, gmail_drafts_update, gmail_drafts_get, gmail_drafts_list |
| Sending | gmail_messages_send, gmail_drafts_send, gmail_messages_reply |
| Reply routing | gmail_messages_list, gmail_messages_get, gmail_labels_create, gmail_labels_add, gmail_labels_remove, gmail_messages_move_to_label (takes `label_id` string, not `label_ids` array) |
apollo_people_match_bulk. Apollo's tool description warns about this explicitly — looping single-match calls burns credits and is much slower.gmail_drafts_create, show them to the user, get explicit approval, then batch-send the rest with gmail_messages_send. Never send a full campaign without showing samples first.cold/<campaign-name> label with gmail_labels_create at the start, apply it to every send, then track replies by searching that label. This is what makes Phase 6 reply routing actually work.references/deliverability.md for warming and per-day pacing.Worth exploring?) beats meeting requests on cold touch 1.unsubscribed label on any "remove me / not interested" reply and never re-target that address from the same Hyper workspace.Get the user to commit to:
gmail_labels_list to verify the integration is live.)If they're stuck on any of these, push back. A campaign without proof or a clear ask will not perform regardless of how clever the writing is.
# Search by ICP
apollo_people_search(
person_titles=["Head of Growth", "VP Growth", "Director of Growth"],
organization_num_employees_ranges=["11,50"],
person_locations=["United States"],
per_page=50,
)Then for the prospects you actually want to contact, batch-enrich for emails:
# CORRECT — one bulk call for many prospects
apollo_people_match_bulk(
details=[
{"first_name": "...", "last_name": "...", "domain": "..."},
...up to 10 per call...
],
reveal_personal_emails=True,
)Only fall back to apollo_people_match for single-prospect lookups (e.g., the user pastes one LinkedIn URL).
For deeper company-level context (industry, revenue range, tech stack), call apollo_companies_search by organization name on the companies you want to enrich. Note that person search results already include core company fields (headcount, industry, location) — only reach for apollo_companies_search when you need data beyond what the person search returns.
For each prospect, gather one specific observation that connects to the problem you solve. Use the cheapest signal that works:
| Cost | Tool | Use it for |
|---|---|---|
| Free (already have it) | Apollo response fields | Title change, recent role start, company headcount jump, funding |
| Cheap | firecrawl_urls_scrape of the careers / pricing / blog page | "You're hiring 4 SDRs", "Pricing pages says enterprise plan launching", "Latest blog post is about X" |
| Cheap (multi-page) | firecrawl_urls_scrape_batch | Same observation across many sites in one call |
| Medium | firecrawl_branding_extract | Brand voice for the email tone, brand colors if you'll send a follow-up image |
| Higher (conditional) | scrape_linkedin_profiles(profile_urls=[...]) (requires LinkedIn Apify integration — skip if not connected) | Recent post, mutual connection, recent job change, school/employer overlap |
Personalization tiers (use the highest tier you can afford for this campaign):
Anything below Tier 2 should be treated with suspicion — {{FirstName}} swaps don't count as personalization.
Pick a framework that matches the situation. The four shapes that consistently work:
See references/frameworks.md for full examples and when each shape works best.
Subject lines. Short, lowercase, internal-looking. 2–4 words. No emojis, no first names, no urgency tricks. Targets: looks-like-a-colleague-sent-it. Examples: quick question, reply rates, hiring ops, q3 forecast, for {{company}}. Avoid: Increase your revenue 10x!, John, are you free Thursday?, [URGENT] follow-up.
Voice rules.
What to avoid (these are the AI-tells reviewers immediately spot):
Re: / Fwd: subject lines.{{FirstName}} swapped.Drafts-first send pattern (default for any 4+ prospect campaign):
# 1. Create label for the campaign once — capture the returned id
label = gmail_labels_create(name="cold/q3-growth-leads")
campaign_label_id = label["id"]
# 2. Draft the first 1-3 prospects for user review
for p in prospects[:3]:
gmail_drafts_create(
to=p["email"],
subject="quick question",
body=render_email(framework="observation", prospect=p),
)
# 3. Show drafts to user, await explicit approval
# 4. After approval, send and label each message
for p in prospects[3:]:
result = gmail_messages_send(
to=p["email"],
subject="quick question",
body=render_email(framework="observation", prospect=p),
)
gmail_labels_add(message_id=result["id"], label_ids=[campaign_label_id])If the user wants every email reviewed, use gmail_drafts_create for all of them and send via gmail_drafts_send after approval. If the user is confident and the templates are pre-approved (e.g., they've run this campaign shape before), you can skip directly to gmail_messages_send from prospect 1. Default behavior is drafts-first.
3–5 total touches with widening gaps. Each follow-up adds something new — a different angle, fresh proof, a useful resource. "Just checking in" gives the reader no reason to respond.
Default cadence (adjust to the user's situation):
| Touch | Day | Angle | Tool |
|---|---|---|---|
| 1 | 0 | Initial framework (observation/question/trigger/story) | gmail_messages_send |
| 2 | +3 | Reply in the same thread, add a one-line specific proof | gmail_messages_reply |
| 3 | +7 | Different angle (if 1 was observation, try question or value-first) | gmail_messages_reply |
| 4 | +14 | Useful free resource — case study, calculator, teardown | gmail_messages_reply |
| 5 | +21 | Breakup email. "Closing your file unless I hear back. Worth keeping the door open?" | gmail_messages_reply |
Always reply in the original thread (gmail_messages_reply with the message_id returned from the touch-1 send) — preserves context and improves deliverability. See references/follow-up-sequences.md for angle rotation, breakup-email templates, and how to prune prospects mid-sequence.
# Pull all replies on the campaign label from the last 7 days
gmail_messages_list(query="label:cold/q3-growth-leads is:unread newer_than:7d")For each reply, read the body with gmail_messages_get(message_id=...), classify it, and label:
| Classification | Label | What to do |
|---|---|---|
| Interested ("yes / tell me more / send a calendar") | cold/q3-growth-leads/interested | Stop the sequence. Hand off (eventually crm-revops once shipped). |
| Objection ("we use X / no budget / try us in Q4") | cold/q3-growth-leads/objection | Reply with one specific response, then stop sequence. |
| Not now ("circle back later") | cold/q3-growth-leads/not-now | Stop sequence. Re-tag for re-engagement in 90 days. |
| Unsubscribe ("remove me / not interested") | cold/q3-growth-leads/unsubscribed | Stop sequence. Add unsubscribed global label. Never re-contact. |
| Out-of-office | cold/q3-growth-leads/ooo | Pause sequence, resume after the OOO end date in the message. |
Apply classification and clear unread with two separate calls:
gmail_labels_add(message_id=..., label_ids=[classification_label_id])
gmail_labels_remove(message_id=..., label_ids=["UNREAD"])Create the sub-labels once with gmail_labels_create and capture their IDs before the routing loop.
Read each draft against this gut-check. Reject any that fail more than one:
For long-form material — read on demand:
| Reference | When to read |
|---|---|
references/frameworks.md | Choosing a framework, full examples, calibrating tone by seniority |
references/follow-up-sequences.md | Building the multi-touch cadence, angle rotation, breakup email templates |
references/deliverability.md | Gmail rate limits, sender warming, SPF/DKIM/DMARC, list hygiene, blocklist recovery |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.