lastlook is a command-line tool for cold outreach. Point it at a live Instantly or HeyReach campaign and it pulls your real leads, renders every variant against each one, and runs 35 checks on the message each lead would get.
$ pip install git+https://github.com/hernangimeno/lastlook
You get one verdict you can gate the launch on, a to-do list ordered by how many leads each problem hits, and a CSV of every lead that broke. Or read the 35 checks first.
Every check is deterministic, and an audit runs all of them on 100% of the rendered messages. Anything that could not run gets named in a NOT CHECKED block, so a rule that sat out never counts as clean. You get a verdict you can gate a launch on, and a recap that groups the fixes by the action that clears them.
================================================================ 🔴 NOT CLEAR: 6 distinct BLOCKER(S), 6 WARNING(S) ================================================================ Rendered 9 messages. 3 of 3 leads would send with a blocker. check issues leads example 🔴 BLANK_MERGE 4 4 e.g. {{company}} resolved blank with no fallback: sends a gap 🔴 EM_DASH 1 3 e.g. subject: banned dash “following up — Acme” 🔴 CLAYGENT_APOLOGY 1 1 e.g. body: AI/enrichment artifact “BOB, I couldn't find recent in 🟡 LEGAL_SUFFIX 2 4 e.g. subject: legal suffix in name “is Globex Inc.'s paid spend…” 🟡 CASING 1 3 e.g. body: shouting name “BOB” (raw CRM casing) 🟡 DOUBLE_PUNCT 1 1 e.g. body: doubled punctuation “…building at Globex Inc.. We help 🟡 AB_SIGNAL_COLLISION 1 1 e.g. variants ['1A', '1B'] share signal “overspend” in step 1 🟡 THREAD_BREAK 1 1 e.g. follow-up sets its own subject “following up — {{company}}” ================================================================ ================================================================ START HERE → Clean the raw values in Clay/CRM (~40 min) ================================================================ 1. [MUST] Clean the raw values in Clay/CRM: 3 leads · Title-case the raw CRM values · Blank the failed AI-enrichment values · Strip legal suffixes from company names ~40 min: step 1 variant 1A, step 1 variant 1B, step 2 variant 2A 2. [MUST] Edit the campaign copy: 3 leads ~2 min: step 2 variant 2A 3. [MUST] Add fallbacks, or enrich the blank fields · Add fallbacks or enrich the blank fields · Fix punctuation at the merge seam ~20 min: step 1 variant 1A, step 1 variant 1B, step 2 variant 2A 4. [then] Fix the campaign settings: 2 places · Give colliding variants distinct signals · Clear the follow-up subject so it threads ~7 min: step 1 variant 1A+1B, step 2 variant 2A Clean: 27 of 35 checks found nothing. 4 fixes, roughly 1.1h total. lastlook can fix 2 of these for you (DOUBLE_PUNCT, EM_DASH). lastlook fix campaign.json # show the diff, write nothing lastlook fix campaign.json --apply # push it to the platform
Run audit for the whole thing in one pass, or keep the stages separate when you want to inspect what flows between them. With more than one campaign live, fleet audits a whole list of them and ranks the results worst first.
Fetch the live campaign and its leads into one normalized JSON. Nothing is written back.
lastlook pull instantly --campaign "Q3" -o c.json
Render every variant against every lead. Merge tags, fallbacks, spintax and conditionals resolve exactly as the platform would send them.
lastlook render c.json -o r.jsonl
Run 35 rules over the rendered output and the lead list. You get a verdict, a recap and a findings CSV.
lastlook check r.jsonl --campaign-json c.json
See a unified diff of the mechanical fixes. It writes nothing until you type the campaign's name.
lastlook fix c.json --apply
Your sequence builder shows the template. Your prospect gets the render. The bugs that burn a lead live in the gap between those two, and no campaign UI shows you that gap.
It catches blank fields, tags with no value source, literal tags shipping, and merge syntax the renderer can't parse. A campaign that renders zero messages is exit 3, not a verdict.
Your enrichment column wrote an apology, and it's sitting mid-sentence in a live email.
It flags shouting names, legal suffixes, full names in greetings, mojibake and placeholder names.
The lead check looks for duplicates, role inboxes, invalid addresses, free-mail in a B2B list, and too many contacts at one domain. These are the only failures that burn the sending domain instead of one message.
Docs paste leaves invisible characters behind. lastlook finds those, plus spam vocabulary, editing scaffolding, and any banned terms you supply. It checks the templates and the rendered output, because a previous client's name usually arrives through the data.
It reads the sequence as a whole: duplicate A/B variants, shared opening lines, a missing first-touch subject, follow-ups that break threading, steps with no gap between them. The near-misses get tested too: an empty follow-up subject is how threading works, so lastlook says nothing.
Every rule ships with a test that fires it and a near-miss that must stay quiet. "we test your pipeline" doesn't trip the placeholder rule; a bare TEST does. An en dash inside a number range is correct typography and passes.
lastlook suggests, you approve. Nothing gets written back until you give the OK. Bad lead values export as a CSV of current and suggested values, ready for whatever owns your data.
step 2 / variant 2A / subject [prose_dash] @@ -1 +1 @@ -following up — {{company}} +following up - {{company}} 2 data value(s) to correct -> lastlook.fixes.csv company_name 'Globex Inc.' -> 'Globex' first_name 'BOB' -> 'Bob' 1 template edit(s). Nothing written. Re-run with --apply to push them to the platform.
All it needs is your Instantly or HeyReach API key. No account, no signup, no server. It asks once, hides what you type, and stores the key on your own machine in a .env only you can read. It goes to your platform and nowhere else.