Daily Outreach Agent
An agent that runs my job search every morning beforeI'm awake.
I built an agent that runs my entire job search loop every morning. It searches 11 job titles across LinkedIn, finds the right hiring managers, verifies their emails through a 4-step cascade, drafts outreach in my voice, and auto-sends between 7 and 8 AM. I review the drafts the night before. The agent does the rest.It's been running daily since February.
The problem.
Job searching is a part-time job on top of a full-time job. The actual useful work — finding the right roles, finding the right contacts, writing personalized notes — gets crowded out by the busywork of finding, verifying, and tracking everything.
I wasn't trying to spray-and-pray. I was trying to send 5–10 highly personalized emails per day to people I genuinely wanted to talk to. The bottleneck wasn't writing. It was the 45 minutes of researchrequired before each email — pulling the contact, verifying the email, checking they were still in the role, drafting something that didn't sound generic.
Constraints.
Four rules going in:
- 01I stay in the loop on quality. The agent could research and draft, but I had to approve every email before send.
- 02Email verification near-perfect. Bounces hurt deliverability on my main account.
- 03Tiny cost. This is a personal system, not a startup.
- 04Readable. I had to be able to see what it sent. No black-box agents.
What I tried first.
First version was a Claude Project I'd open every morning, paste a list of jobs into, and ask for outreach. It worked — kind of. The drafts were good. But the loop was still manual. I was still doing the LinkedIn searches, still pulling the contacts, still running each email through Hunter and crossing my fingers.
It was a tool, not a system. The 45 minutes of busywork was still 45 minutes of busywork.
The second version moved everything into n8n. That worked too — but n8n was overkill for what was effectively a daily cron job over a Google Sheet. I was maintaining workflows for a problem that didn't need workflows.
What actually worked.
The version that stuck is built on Claude Co-Work, Google Sheets, and a small Apps Script file. Five components, each doing one thing.
01 · The morning search.
Co-Work runs at 6 AM. It searches LinkedIn for 11 job titles I care about — Growth Engineer, GTM Engineer, AI Solutions Architect, AI Consultant, and a few variants. For each new role, it pulls the company, the role description, and 2 likely hiring contacts.
02 · The verification cascade.
This is the part most automated outreach systems get wrong. They guess emails. I refuse to guess. The agent runs each contact through a cascade: LinkedIn → Prospeo → Hunter → Apollo. If a service finds the email, it stops. If none of them find it, the contact gets flagged as send LinkedIn DM instead. We never invent an email. Bounces are how your domain reputation dies.
03 · The voice-matched drafts.
Each verified contact gets a draft. The drafts are short — under 80 words, hand-written templates with 2–3 LLM-filled variables. Same architecture as Channel Fusion. The LLM doesn't write the email. It picks the right template and fills in the casual icebreaker and the company name.
04 · The Google Sheet as a database.
Every contact, every status, every email lives in a single sheet. Column Q is the status: Yes, No, Sent, Send LinkedIn DM, Bounced — send LinkedIn DM. The whole system is readable in a single tab. Apps Script handles all the writes through a small AddOutreach.gs file.
05 · The auto-send and bounce loop.
Two scheduled Apps Script functions handle the send side. sendApprovedEmails runs between 7 and 8 AM on weekdays. It finds every row marked Yes, sends the email through my Workspace account, and updates the status to Sent. checkBounces runs an hour later — 9 to 10 AM. If a sent email bounced, it flips the status to Bounced — send LinkedIn DM so I know to follow up on LinkedIn instead.
// runs 7–8 AM weekdays. small and boring on purpose. function sendApprovedEmails() { const sheet = SpreadsheetApp.getActive().getSheetByName('Outreach'); const rows = sheet.getDataRange().getValues(); rows.forEach((row, i) => { if (row[STATUS_COL] !== 'Yes') return; GmailApp.sendEmail(row[EMAIL_COL], row[SUBJECT_COL], row[BODY_COL]); sheet.getRange(i + 1, STATUS_COL + 1).setValue('Sent'); }); } // runs 9–10 AM. flips bounced rows to LinkedIn DM track. function checkBounces() { const threads = GmailApp.search('from:mailer-daemon newer_than:1d'); // match bounced addresses back to sheet rows, update status }
The whole thing is maybe 200 lines of Apps Script and a sheet. No servers. No deployments. No subscriptionsbeyond the verification services I'd be using anyway.
The numbers.
The agent has run every weekday since February. It identifies 5–15 new roles per daydepending on how active the market is. The verification cascade resolves about 75% of contacts to a real email; the rest go to LinkedIn outreach. Bounce rate has stayed under 1% — clean enough that my domain reputation hasn't moved.
I spend roughly 15 minutes a night reviewing drafts. I used to spend 90.
What I'd do differently.
- 01Build the verification cascade first. I built it third. The first two weeks of bounces taught me the lesson the hard way.
- 02Skip the n8n version entirely. I lost two weeks rebuilding something that worked fine in Apps Script.
- 03Add a "snooze" status to the sheet earlier. Some companies are interesting but not now. I had to retrofit the logic to handle that.