AI automation · GTM workflows
I don't just talk about automation. I build things that actually run.
Pick a workflow below and watch example data go through it: what comes in, the rules it follows, and what comes out.

Workflows I run in production
Apps and side builds
Things you can open and try.
A GTA-style game of Copenhagen, modern and historical.
Play it → Cloudflare · D1 · ClaudeNutrition trackerPhoto of my plate in, macros out. Built in about 4 hours.
Clickable preview → Cloudflare WorkersVibesetPick a mood, get a full-length DJ set from YouTube.
Open the app → Python · Flask · GeminiSmart job searchPaste a job ad and a CV, get a fit score, gaps and a cover letter.
On GitHub → TypeScript · Anthropic APIcontent-agentWrites long-form content in a set brand voice, drafts to Notion.
On GitHub →How it's built
The full node maps, what changed between versions, and the JSON where I share it.
How it works
The problem it solves. When someone clicks a Microsoft (Bing) ad, Microsoft can see the click, but it loses track of what happens after that. The person might fill out a form, become a lead in HubSpot, turn into an SQL a few days later, and eventually become a customer. Microsoft sees almost none of that.
That means its bidding algorithm is learning from clicks and form submissions, rather than from the people who actually become customers.
Offline conversion tracking fixes that. Every ad click comes with a unique msclkid. I store that ID on the HubSpot contact, and when the contact later becomes an MQL, SQL, or closed-won customer, I send that information back to Microsoft. For closed-won deals, I can even send the deal value.
So instead of learning, "this type of person fills out forms," Microsoft can start learning, "this type of person actually becomes a customer."
How the direct API version works. HubSpot sends a webhook to n8n whenever something relevant changes on a contact or deal. From there, the workflow checks what happened and decides whether it needs to send a new conversion to Microsoft.
For example, if a contact becomes an MQL or SQL, it first checks whether they actually came from a Microsoft ad by looking for the msclkid. Organic leads are ignored. It also checks whether that conversion has already been uploaded, so the same person doesn't get counted twice.
If everything looks good, n8n sends the conversion directly to Microsoft's Offline Conversions API. For closed-won deals, it also sends the deal value in EUR.
Once Microsoft has received it, the workflow updates a property in HubSpot to basically say: "We've already sent this one." That small step is what stops duplicate conversions if HubSpot triggers the workflow again later.
Same workflow, second implementation — via Google Sheets
Why I built a second version
Calling Microsoft’s API directly is probably the cleaner setup. But there’s another option: Microsoft Ads can also import offline conversions from a Google Sheet on a schedule.
It’s a bit less elegant, but I actually like it for a few reasons. You can open the sheet and see exactly what’s being sent to Microsoft, it’s easier for someone else to troubleshoot, and you don’t have to rely as much on API authentication working perfectly.
So instead of sending conversions straight to Microsoft, this version writes them into a Google Sheet. Microsoft then imports that sheet automatically.
The part I like most is actually the boring bit.
Once a week, the workflow checks the sheet, keeps the last 14 days of conversion data, removes everything older, and writes the recent rows back.
It sounds like a small detail, but it matters. Microsoft keeps coming back to the same sheet to import conversions. If you never clean it up, you eventually end up with months or years of old data sitting there and being checked again and again.
That’s the kind of thing you probably wouldn’t bother building into a quick demo. But if the automation is supposed to keep running for months without anyone thinking about it, it suddenly becomes important.
What changed from v1 → v2
The first version worked, but once I started running it every day, the weak spots became pretty obvious. So v2 is basically the version I wish I’d built the first time. I’m keeping both here because I think it’s useful to see how the workflow changed.
LLM “memory” → actual state. In v1, I basically told the LLM: “rotate through the countries and don’t repeat yourself.” Which works... until it doesn’t. Language models aren’t databases. In v2, a code node keeps track of how often each country has been used, so the rotation is predictable and doesn’t repeat.
One lead at a time → batches. v1 processed leads individually and waited 30 seconds between requests. Fine for a demo, painfully slow in practice. v2 checks new leads against everything already processed, then sends them in batches of 25 for enrichment and 50 for campaign creation. It also retries failed jobs up to 12 times instead of waiting around indefinitely.
Guessing → saying “I don’t know.” The first version always tried to come up with an email address, even when the evidence was weak. That’s obviously not great if you’re actually going to email people. v2 validates addresses, filters out personal email domains, and tells the model to return UNKNOWN when it isn’t confident. Those leads simply get dropped.
Silent failures → Slack alerts. This was probably the most important change. If an API call fails, v2 sends me a Slack message with the error and the payload that caused it.
It’s a small addition, but it’s also the kind of thing that separates a workflow that looks good in a demo from one you can actually leave running every day.
How the lead machine works
The problem it solves. Finding the right people takes time. Finding a decent work email for each of them takes even longer. This workflow does both every morning before I’ve had coffee.
Two ways of finding leads. Each day, it generates a new Google search like site:linkedin.com/in "Warranty Manager" "Denmark", rotating through different job titles and countries. The workflow keeps track of what it has already searched, so it doesn’t just run the same query again tomorrow.
At the same time, Apollo searches for people matching the same profile. The two sources are then combined, duplicates are removed, and the leads move through the rest of the workflow in batches.
Enrichment first, AI second. Dropcontact gets the first shot at finding a verified business email. If it comes back empty, GPT-4o tries to work out the most likely address from the person’s name and company domain.
But I didn’t want the AI confidently inventing email addresses, so there are a few checks after that. The address has to pass basic validation, personal domains are filtered out, and if the model isn’t confident enough, it returns UNKNOWN. Those leads get dropped instead of emailed.
Then it hands the interesting part back to me. Qualified leads are added to an Instantly campaign. The next day, the workflow checks for replies. If someone responds, I get a Slack notification and take over from there.
That’s basically the idea: automate the repetitive part of finding and reaching the right people, then bring a human in as soon as there’s an actual conversation.
One important caveat: AI-inferred emails are still guesses, not verified addresses. I’d always run them through a proper bounce check before sending. And if you’re doing cold outreach in the EU, make sure your setup actually complies with the rules that apply to you.
How the content agent works
The problem it solves. Coming up with content ideas is easy. Knowing which ones are actually worth writing is harder. This workflow starts with a Semrush gap analysis and finds keywords competitors rank for that we don’t, then filters them by search volume and difficulty.
It checks what we’ve already written first. Before choosing anything, it looks at our Notion content calendar and removes topics we’ve already covered. From what’s left, it picks ten keywords: four with AI intent, four comparison-style searches like “best X” or “X vs Y,” and two wildcards. Easier opportunities get priority.
Then it researches each topic properly. For every keyword, the workflow pulls the current top 10 Google results and uses them to understand what is already ranking and where there might be room to add something better.
That context gets turned into a brief, and Claude writes the first draft using a fairly strict set of rules around tone, banned words, internal links, FAQs, HTML components and formatting.
I also made the output deliberately strict. Claude has to return one valid JSON object. If the structure is broken, the article gets rejected rather than quietly passing bad data into the next step.
It creates drafts, not finished articles. Everything ends up in Notion with the article, title tag, meta description, slug and target keywords ready for review.
That distinction matters to me. The automation can do a lot of the research and get you surprisingly far with a first draft, but I still want a person deciding whether something is actually worth publishing.
And one bug I found while documenting it: Notion only accepts 100 blocks in a single request. Longer articles were getting cut off, and although the workflow flagged that they were truncated, nothing actually did anything with the flag.
So if you adapt this one, make sure the remaining blocks get appended in a second API call. Exactly the kind of bug you don’t notice in a short test article, but definitely notice once the workflow starts running for real.
How the meeting prep works
The problem it solves. Account executives walk into meetings cold, or burn their mornings digging through the CRM. This does the digging before anyone is awake.
Only the meetings that matter. At 07:00 it reads the day's calendar and drops everything internal — if every attendee is on the company domain, no prep is generated. For external meetings it takes the guest's email domain and looks the company up in HubSpot: firmographics, plus every open deal and its stage.
A brief with a fixed skeleton. Claude writes against a strict structure: who's in the room, company snapshot, where the deal stands, this meeting's one outcome to push for, tailored questions, likely objections with answers, the closest customer proof point, recommended next step. The prompt's best line: "If context is thin, say so rather than inventing."
Delivered where the AE lives. The brief is staged in Notion, a note is logged on the HubSpot company so there's a paper trail, and the AE gets a Slack DM with a one-line summary and the link.
Full disclosure: reviewing v1 for publication I found three real defects — the parse step computed the brief and never returned it, the Notion step was referenced but missing entirely, and the calendar pull had no date window (it would prep every meeting ever booked). All three are fixed in the version you'll receive, marked in the node notes.
How the lead screener works
The problem it solves. Building a prospect list by hand eats SDR hours, and the quality depends entirely on who's doing the digging. This turns it into a form: type an industry, a geography and a number, get back a screened list.
It's an agent, not a pipeline. Unlike everything else on this page, there's no fixed sequence in the middle. A research agent — Claude with live web search as a tool and session memory — keeps searching, reading and collecting until it has the requested number of real companies matching the profile. It decides its own next search based on what it's found so far.
The agent doesn't get the final say. Every company it proposes goes through a code-based screen: is it a real, established company, does it sell physical products, is it the right size? Then a dedupe check against every lead from every previous run — the workflow remembers what it has already delivered, so nobody gets prospected twice.
Everything is logged. Survivors land in a Google Sheet, every run writes an audit row — who asked, for what, how many made it through — and the final list goes to Clay for enrichment before outreach.
Worth knowing if you adapt it: the download uses placeholder references for the prompts and screening code — that's deliberate. The ICP rules are the whole product here; you have to define what a qualified prospect means for your business before this is worth running.
How the visitor-to-pipeline flow works
The problem it solves. Somewhere around 95% of B2B website visitors never fill in a form. They read the pricing page, maybe an integration page, and leave. RB2B de-anonymizes a chunk of that traffic — you get the person and the company, not just an IP.
n8n is the router. RB2B’s integration fires a webhook per identified profile. A code node normalizes the payload, and an IF node splits the stream: company records go to one Clay table, people to another. Two HTTP calls, done. It’s a small workflow, but it’s the load-bearing joint between identification and enrichment.
Clay does the thinking. The company table enriches every domain — industry, employee count, revenue, city, which page they visited — and runs the qualification columns. The people table enriches the person: title, LinkedIn, work email. Crucially, Clay also checks the CRM first: is this contact already in HubSpot? Is there already a meeting booked? If yes, the flow stops right there — nothing kills trust faster than cold-emailing someone your AE met last week.
Qualified people go to Instantly. Contacts that pass the ICP screen get pushed into an Instantly campaign. The messaging can reference what they actually looked at, because the visited page travels with the record.
The loop closes on replies. When someone answers, Instantly fires a webhook back into a dedicated Clay table. From there two things happen at once: a Slack notification with the full context, and an upsert into HubSpot. The SDR sees the reply minutes after it lands, with the whole story attached — who they are, what they visited, what they were sent.
Worth knowing if you adapt it: the qualification columns are the whole product here. Which industries, what size, which pages signal intent — that’s your ICP encoded in a table, and it’s different for every business. Budget a real afternoon for those columns and treat the rest as plumbing.
How the three scanners work
The problem it solves. ABM lists go stale the day they're built. The accounts don't change, but their timing does — and timing is the whole game. These three workflows exist so the team hears about a trigger event the week it happens, not the quarter after.
One skeleton, three lenses. Each scan starts the same way: pull the target accounts from HubSpot, build a query matrix (company × role × source), fan out to the sources in parallel, extract structured signals with code nodes, merge and dedupe to one signal per account. Only the sources differ. Hiring hits Adzuna and SerpApi for role listings, probes the account's own ATS board, and cross-checks the Swedish JobTech API. Launches queries Google News, diffs the homepage against last week, and discovers and reads the account's RSS feed. Intent searches role postings for titles that only get hired when a company has the exact problem we solve.
Claude does two jobs. First it classifies and screens: what kind of signal is this, how strong, and does the account still fit the ICP? Second it writes the opener — one line an AE can paste into an email that references the actual trigger ("saw you're hiring a second RevOps lead…"). The prompt is told to prefer the specific over the clever.
Where it lands. The signal, its strength and the opener are written as properties on the HubSpot company, so they show up in the AE's normal views with no new tool to check. A weekly Slack report summarises the batch. Accounts that surface in the intent scan but aren't in HubSpot yet are pushed to Clay for enrichment and an ICP verdict before anyone decides whether to add them.
Worth knowing if you adapt it: the query matrix and the intent role list are the product. Which titles signal your problem is a two-hour conversation with sales, and it's different for every business. Everything else is plumbing you can copy.
How the benchmark works
The problem it solves. Every ZoomInfo search costs credits and gets thrown away after the SDR skims it. This turns each search into a permanent, ICP-labelled row, so the next person asking "how big is the Nordic retail segment, really?" gets an answer from data instead of a fresh search.
Queue first, so it can't run away. Requests go through a form into a Google Sheet. A scheduler claims one row at a time and marks it processing — so two people submitting at once can't double-spend credits, and a crash mid-batch leaves a visible, resumable state in the sheet rather than a half-finished mystery.
Annotate, don't filter. Search results are cross-checked against every HubSpot company. Matches are tagged, not removed — the benchmark should show the whole segment including accounts already in the pipeline, otherwise the totals lie.
Evidence before verdict. For each company it fetches the homepage and the aftersales / returns page (the page that tells you whether they actually have the problem), extracts the text, and only if the site is unreachable falls back to a SerpApi snippet. Claude gets enrichment + page text + search evidence in one bundle and returns a fit verdict with reasoning. That bundle-then-judge shape is deliberate: the model never scores on firmographics alone.
Save everything. Fit or not, the row is appended — verdict, reasoning, evidence source. A Slack digest closes the batch and the queue row is marked done. There's a separate error trigger that checks for a provider outage before it pages anyone, so a ZoomInfo blip doesn't wake the SDR at 03:00.
How the rescoring works
The problem it solves. An ICP screen is only as good as the evidence it's shown. When the first pass scored a company on thin data — a parent-company name, a blocked site, a domain that doesn't match the brand — the verdict is a guess. This workflow exists to replace guesses with a documented second look.
Evidence, not just firmographics. Targets come from a curated sheet. Each is re-enriched via ZoomInfo by website rather than name (the name is usually what was wrong), then the homepage and aftersales page are fetched and reduced to text. Reachability is recorded explicitly, and only unreachable sites trigger the SerpApi fallback — so the scorer always knows how good its evidence is.
Same judge, richer brief. The evidence is merged into one scorer input and Claude returns a verdict plus reasoning. The prompt is the same rubric the benchmark uses, which is the point: a different answer has to come from different evidence, not a different question.
Drift stays visible. Each row in the rescore sheet carries the previous verdict, the new one, and the reasoning. A flip from "not a fit" to "fit" is what the SDR is looking for; a confirmation is still useful because it closes the question.
Where it started
12 automations in 12 days.
I moved to Copenhagen in 2024. Applications weren't working, so I built one automation a day with free tools and posted each one.

Day 1 · Make · together.ai
Auto-generate LinkedIn posts with AI
Read the build →
Day 2 · Make · Apify
A job scraper that writes the cover letter
Read the build →
Day 3 · Clay
A startup intelligence table in Clay
Read the build →
Day 4 · n8n
An AI-written daily newsletter
Read the build →
Day 5 · Zapier
Customer reviews into ad copy
Read the build →
Day 6 · Pipedream
A daily quote bot
Read the build →
Day 7 · Gemini
Freelance briefs from Trello keywords
Read the build →
Day 8 · Buffer
A content translation pipeline
Read the build →
Day 9 · n8n
A lead qualification system
Read the build →
Day 10 · Enrichment
Real-time customer journey enrichment
Read the build →
Day 11 · Ads + AI
Ad campaign optimisation with AI
Read the build →
Day 12 · Replit
An inbox copilot that drafts replies
Read the build →Got a process eating hours every week?
Tell me how it works today. I'll tell you if I can build something for it, and test it on real data before any proposal.
Book a 15-min call
NowGTM at Claimlane
Latest postLatest from the blog













