A case against automation
AI made personalized outreach free, so personalized is now the minimum. We built the cheap version, tried cold calls, and build proposal pages by hand now.
In this note · 6 sections
Every day, I wake up to around 3-4 LinkedIn DMs from companies trying to sell me various business services. They tell me it’s because they “saw that I’ve been working at _____ company for ___ number of years.”
After ignoring all of them, I check my email to find 3 emails in my inbox from other dev agencies offering their software engineer services because they “saw my GitHub and noticed _____ project hasn’t moved in a considerable amount of time.”
All of this personalization is oddly…impersonal.
AI made outreach basically free, and it started what I like to call “The AI arms race.” Business developers everywhere saw AI and thought “wow, now we can send thousands of individualized DMs,” and yes, of course we did the same.
My mind immediately started thinking about all of the ways we could mass produce thousands of DMs across various channels, and make all of them sound hyper-personalized. “They will never know it’s AI,” I would tell myself.
So we built a system that I like to call the “28-line generic slop generator.”
The 28-line generic slop generator
The signal we picked was job posts for a remote JavaScript developer. If a company posts that role, it means: 1) they need JavaScript developers, and 2) they are fine with remote devs.
Most startups host their job board on Greenhouse or Lever, and both have public APIs. So the whole pipeline is one API call, two filters, and one model call:
import json, urllib.request JS_WORDS = ("javascript", "typescript", "node", "react", "frontend", "full stack") PROMPT = """Rewrite this outreach message so it sounds casual and personal.Do not use the same opening twice. Mention {role} and {company}.Keep it under 60 words. Message: Hey {name}, saw {company} is hiring a {role}. Hiring takesmonths, and our team can start next week. Open to a quick chat?""" def open_js_roles(board: str) -> list[str]: url = f"https://boards-api.greenhouse.io/v1/boards/{board}/jobs" with urllib.request.urlopen(url) as r: jobs = json.load(r)["jobs"] return [ j["title"] for j in jobs if "remote" in (j["title"] + " " + j["location"]["name"]).lower() and any(w in j["title"].lower() for w in JS_WORDS) ] def draft_dms(leads: list[dict], llm) -> list[str]: dms = [] for lead in leads: for role in open_js_roles(lead["board"])[:1]: prompt = PROMPT.format(name=lead["name"], company=lead["company"], role=role) dms.append(llm(prompt)) return dmsNothing about this looks special, until you look at our ultimate differentiator (sarcasm): the personalization.
Rewrite this outreach message so it sounds casual and personal.
Do not use the same opening twice. Mention {role} and {company}.
Keep it under 60 words.
Read that prompt again. “Do not use the same opening twice.” We asked an AI to hide the fact that an AI wrote the message. That one line is the entire outreach industry right now.
How it went
Here’s the kind of thing it wrote:
Hey Priya! Hope your week is off to a great start! Saw Acme is hiring a Remote JavaScript Developer. Hiring takes forever, and our team can start next week. Worth a quick chat?
Priya, quick one. Finding a good remote JavaScript dev in this market is tough! We can help you ship while you search. Open to 15 minutes?
Hey Priya! Noticed Acme is hiring for Remote JavaScript Developer (Evergreen Pipeline). Our team can start next week...
You might be getting PTSD reading these messages if you’ve opened your LinkedIn inbox in the last few months.
If these messages make us cringe, they make everyone cringe. We didn’t send a single one.
Then we tried humans
We dropped the generator and went the other way. We hired a cold caller. “This will do the trick,” I thought.
It didn’t work either. A cold call from a real person still sounds like every other cold call. The prospect has no reason to think that we are different, because we didn’t show them anything different. Ugh. This is starting to get frustrating.
Do things that don’t scale
One day, I was randomly scrolling on LinkedIn, feeling defeated, and saw a post about how Y Combinator tells founders to do things that don’t scale. It made me think: what cannot possibly scale? Something so crazy, and so hard to scale, that most people would think “there is no way I have the time for that.” Coincidentally, right after, I saw a post by Dan James about how he traveled to freaking Lebanon to film a video for a founder from there. That video got the founder’s attention. Okay, I wasn’t willing to fly all the way to Lebanon, but I did find the answer. Our Lebanon would be a page. In this AI world, that is the thing that will make us different.
The new bar: a proposal page per company
When we want to work with a company, we build them their own proposal page at xors.xyz/for/<company>. It shows what we found in their business, our past work that matters to them, and the people who will do the work. Then a person on our team writes a short message and sends the link.
Yes, we do utilize AI here. It helps us with the design and gets the first version of a layout on screen fast.
But the research is mostly human. One of us reads their product, docs, job posts, and sometimes their code. Yes, we go onto Google, click links, read things, and then generate copy for the landing page. Then that person decides what is worth a spot on the page. The message that we send them to deliver this page is human too.
Can you AI-ify this?
Probably. Someone will build exactly that, and soon every inbox will have a “custom page” in it too.
But the easier something is to replicate, the less value it gives the person who gets it. And the less value it gives, the less people will even listen. That’s the AI arms race in a nutshell: whatever scales gets copied, and whatever gets copied stops getting replies.
The goalposts are moving fast. So while this is working for us now, things will likely change for us later down the road.