{"id":78447,"plugin_id":"plugin_asdk_app_6adaa07e3fdc81918f58b738e69737a5","kind":"skill","collection_source":"plugin_package","comparison_source":null,"observed_at":"2026-11-03T12:13:50.880Z","digest":"795f36c59739b9afacdf97a960d9ebb8ea2d7b5eba98fb9298e58aab03903004","against":null,"payload":{"description":"Reach companies when something changes - hiring, a funding round, a new leader, a job change - with a free gate before any spend, one decision maker per company, copy about the plan behind the signal, and a trigger that keeps it running.","included_files":[{"relative_path":"references/signal-copy.md","size_in_bytes":3716},{"relative_path":"references/signal-gate.md","size_in_bytes":3626}],"name":"signal-outreach","skill_md_contents":"---\nname: signal-outreach\ndescription: Reach companies when something changes - hiring, a funding round, a new leader, a job change - with a free gate before any spend, one decision maker per company, copy about the plan behind the signal, and a trigger that keeps it running.\n---\n\n# Signal outreach\n\n\"Email companies hiring SDRs.\" \"Reach companies that just raised.\" A signal is a change at a company that makes now a better time to write: open roles, a funding round, a new sales leader, a past champion's new job, fast headcount growth. This skill turns one into outreach:\n\n1. Pick the signal and the plan it points to.\n2. Write a free, deterministic gate and show what passes before anything is charged.\n3. Find one decision maker per company.\n4. Write copy about the plan behind the signal.\n5. Run it once as a list, or keep it running as a trigger on a campaign.\n\nA person launches the campaign. You build it and hand it over.\n\n## Rules\n\n- **A person approves every launch and send.** You may create a trigger on a Draft campaign. Launching, resuming or raising volume is the person's click, and on a client's campaign it needs a yes that names that client, with its source (who said it, where, when).\n- **A funding round is not a buying event by itself.** In Kenso's own campaign data, people at funded companies reply no more than people at same-size companies with no funding (1.01x). The money pays for a plan, usually hiring first. Write about that plan, to the person who runs it. Never open with \"congrats on the raise\".\n- **Signals are noisy. Gate before you spend.** In Kenso's own hiring-signal runs, only about 1 in 4 matched job titles was the role the signal was meant to find. A free gate on titles, size, country, language and duplicates removed about 70% of signals before anything was charged.\n- **Never invent** a round size, a job title, a date or a name. Quote the signal as the source states it, with its date. An amount the source leaves blank is \"not stated\", never 0.\n- **Never name a data vendor** to a client. Signals come from Kenso.\n- Skip companies already worked elsewhere: target accounts (the Accounts lane works them), live conversations and the blocklist.\n\n## Step 0 - Check what is on\n\n- The trigger tools are open to every member; creating, changing or pausing a trigger needs full access (admin). A 403 means the user is not an admin: say so in one sentence and stop.\n- Trigger capacity (`get_campaign_trigger_capacity()`, `set_campaign_trigger_cap()`) is for admins only. If they are missing, skip the capacity step and tell the user an admin sets it.\n- Job postings answer 503 `service_unavailable` when the index is off. Tell the user and do not retry in a loop.\n- Signals on target accounts are not readable here. Use the searches below.\n- If the workspace has no campaign to attach a trigger to, build one first (Step 5). If none of the operations here are in your tools, tell the user in one sentence and link `https://app.ken.so/<workspace id>`.\n\n## Step 1 - Pick the signal and the plan it points to\n\nAsk what the user sells and what change makes a company need it. Then pick the source:\n\n| The user says | One-off list (Mode B) | Always on (Mode A trigger `signal_type`) |\n|---|---|---|\n| \"companies hiring SDRs / AEs / RevOps\" | `search_job_postings()` on the titles | `open_roles` (open posts that match the titles), or `hiring_surge` (net adds in a function) |\n| \"companies that just raised\" | `search_funding_rounds()` | `fundraising` |\n| \"new VP Sales / new CRO\" | `search_companies()` with `leadershipChange` | `new_in_seat` (people new in their seat) |\n| \"people who used us and changed jobs\" | - | `job_change` |\n| \"fast-growing companies\" | `search_companies()` with `hiringSurge` or `headcountGrew` | `headcount_growth`, `company_headcount_growth` |\n\nRead the exact trigger fields and defaults before you write one: `list_trigger_signal_types()`.\n\nThen name the plan the signal points to, in one line: \"They raised to build an EMEA sales team: three AE roles are open.\" That line drives the gate's titles, the person you write to and the copy.\n\n## Step 2 - Gate it for free\n\nEvery search here is free. Browse, count and filter before anything is imported or enriched.\n\n**Hiring:**\n\n`search_job_postings(body={\"titles\": {\"include\": [\"sales development representative\", \"business development representative\"], \"exclude\": [\"intern\"]}, \"location\": {\"countries\": [\"US\", \"GB\"]}, \"postedWithin\": 30, \"groupBy\": \"company\", \"includeDescription\": false, \"includeFacets\": true, \"limit\": 50})`\n\nResolve partial names first with `resolve_job_posting_filters(body={\"titles\": \"sales devel\"})`. Staffing agencies are hidden unless `includeAgencies` is true.\n\n**Funding:**\n\n`search_funding_rounds(body={\"reportedWithinDays\": 30, \"newRoundsOnly\": true, \"amountRaisedUsd\": {\"min\": 2000000}, \"hasDomain\": true, \"includeFacets\": true, \"groupBy\": \"company\", \"limit\": 50})`\n\nFunds, real estate and banks are hidden by default. `location.states` takes US state codes.\n\n**Company changes** (the company search, six countries): `search_companies(body={\"hiringSurge\": {\"functions\": [\"Sales\"], \"windowMonths\": 3, \"minNetAdds\": 3}, \"companySizes\": [\"11-50\", \"51-200\"], \"countries\": [\"United States\"], \"page\": 1, \"pageSize\": 25})`. Filter keys are in the `ken-search` skill's reference.\n\nThen apply the gate, cheapest check first, and keep the count after each one:\n\n1. **Titles**: keep only titles that match the plan; drop the families in `references/signal-gate.md` (recruiting for clients, IT implementation roles, incidental words, part-time and entry-level posts, roles that do not own pipeline). Read the real title, never the keyword that matched.\n2. **Size and country**: the user's ICP. The people search covers six countries (United States, United Kingdom, Canada, Ireland, Australia, New Zealand): a company elsewhere passes the gate but gets no decision maker in Step 3. Say how many.\n3. **Language**: drop postings not written in the campaign's language.\n4. **Duplicates**: one row per company domain. Drop companies already in the campaign. Target accounts are not readable here: ask the user which of these companies their team already works as accounts, and drop those.\n\nShow the user the funnel before you go on: \"212 postings, 141 companies, 46 pass titles, 31 pass size and country, 29 new. Next: 29 companies, up to 29 people imported into a Draft campaign.\" Price it with `preview_trigger(signal_type=\"open_roles\", parameters='{...}')` (the same keys the trigger stores; it runs for `fundraising`, `open_roles` and `company_headcount_growth`, and saves, fires and charges nothing). Show its `steps` (pass counts per gate step) and `estimatedCredits.basis` (an upper bound for the page, charged per lead), say that each imported person is enriched, verified and qualified by the campaign, and get a yes before Step 3.\n\n## Step 3 - One decision maker per company\n\nWrite to the person who runs the plan, not the logo. At a company over 200 people, the CEO is not buying your tool: the VP who inherited the hiring plan is.\n\n1. Find each company's search id: `search_companies(body={\"website\": \"acme.com\", \"page\": 1, \"pageSize\": 1})`.\n2. Find the person: `search_people_at_companies(account_ids=[123456, 234567], current_titles=[\"VP Sales\", \"Head of Sales\", \"Director of Sales Development\"], seniority_levels=[\"Director\", \"VP\", \"CLevel\"])`. Keep one per company, two at most.\n3. Funding rounds name executives on the round (`people`); use them only when their role owns the plan.\n\nKeep each person's search id. The campaign finds and verifies their email after import. Outside the six countries there is no email find here yet: list those companies for the user instead of importing them.\n\n## Step 4 - Write the copy\n\nThe signal is the reason; the plan is the subject. The shape, examples per signal and the data behind the rule are in `references/signal-copy.md` in this skill's folder:\n\n1. Their plan, in their words: \"Your careers page lists four SDR roles this month.\"\n2. The problem that plan creates: \"New SDRs usually spend their first month without a list to call.\"\n3. What you do about it, with one proof point the user gave you.\n\nEach email quotes its own company's signal. Keep the facts per company (the job title, the posting date, the round's stated use) and let the campaign's AI personalization write that line from the lead's company data. Never write \"congrats\", never guess a number, never claim they are \"scaling\" unless the source says so.\n\n## Step 5 - Run it\n\n**Mode B, one-off list.** Reuse a Draft campaign that has the copy from Step 4, or create one with `create_campaign(name=\"...\")` and have the person set the copy in Kenso. Import the people you picked:\n\n`import_search_results_to_campaign(campaign_id=8123, selected_person_ids=[90011, 90012])`\n\nThe import skips people already in the campaign. A CSV import that carries each company's signal facts as custom variables is not available here: the person runs it in Kenso. Then hand it over: the person reviews and launches it in Kenso. Repeat weekly for a fresh list.\n\n**Mode A, always on.** A trigger watches for the signal and adds each new match to a Trigger campaign by itself. Try the filters with the searches in Step 2 first, then:\n\n`create_trigger(campaign_id=8123, signal_type=\"fundraising\", tier=\"same_day\", parameters='{\"raisedWithinDays\": 30, \"newRoundsOnly\": true, \"minRaisedUsd\": 2000000, \"titles\": [\"VP Sales\", \"Head of Sales\"], \"maxContactsPerCompany\": 2}')`\n\n- `parameters` is a JSON string with camelCase keys, as `list_trigger_signal_types()` gives them. Unknown keys are rejected. Funding-round search keys (`reportedWithinDays`, `amountRaisedUsd`) differ from the trigger's (`raisedWithinDays`, `minRaisedUsd`).\n- The campaign must be in Trigger mode. `switch_campaign_to_trigger=True` switches a Batch campaign in the same call, and a Trigger campaign never goes back to Batch: pass it only when the user agreed to that.\n- `tier` is `minutes`, `hours` or `same_day`: how fast a firing goes out.\n- People a trigger adds to a Draft campaign are not emailed until a person launches the campaign.\n\n**Capacity.** Read `get_campaign_trigger_capacity(campaign_id=8123)`. A daily cap is volume: set it with `set_campaign_trigger_cap(campaign_id=8123, daily_cap=25)` only when the user gave the number, and on a client's campaign only with a yes that names that client.\n\n## Step 6 - Watch the firings\n\n- `get_trigger_stats(trigger_id=77, days=7)`: firings by outcome.\n- `list_trigger_firings(trigger_id=77, status=\"suppressed\")`: why firings were held back (cooldown, frequency cap, do not contact, blocklist, an active conversation, already in a trigger campaign, expired).\n- Too many suppressed or off-target firings: tighten `parameters` with `update_trigger(trigger_id=77, changes={\"parameters\": \"...\"})`. Too few: widen the window or the titles.\n- To check that a saved trigger would fire, test-fire it on a sample signal: `test_fire_trigger(trigger_id=77, contact_id=..., payload={...})`. It reports `shouldFire`, `rejectedBy` and each check, and queues, sends and charges nothing. `payload` is the signal's JSON object. Sweep-only signals (`click_dormancy`, `company_icp_timing`, `linkedin_post_engagement`) answer `test_fire_not_supported`.\n- `pause_trigger(trigger_id=77)` and `resume_trigger(trigger_id=77)` stop and restart it. On a client's campaign both need a yes that names that client.\n\nReport the signal, the gate funnel, who you picked and why, the campaign and trigger ids, and what the person has to click.\n\n## What this skill cannot do yet\n\n- No bulk fit classifier: judge fit from the company data yourself, in batches, and keep a reason per company.\n- No email find outside the people search's six countries.\n"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}