An outbound state machine gives every account a visible status and a reason for the next transition. Tom Bonagura describes a system that checks CRM exclusions before spending on enrichment, holds ineligible accounts with a reason, and rechecks them when the underlying facts may have changed.

Video not loading in LinkedIn or your in-app browser? Watch it on YouTube.

What is an outbound state machine?

It gives each account a named state, a rule for moving to the next state, and a recorded reason when work stops. Tom built this after changes to connected tables and automations kept breaking later steps. A state machine made the path from incoming domain to outreach easier to inspect.

Where should the checks happen?

His flow starts with a new domain from a signal such as a site visit or recent hire. The account enters a queue, then a CRM exclusion check runs before paid enrichment. Existing customers, open deals, and do-not-contact records go on hold with a reason. Other accounts move through triage and scoring. A poor fit can be checked again when the underlying information changes.

That hold state matters. It prevents a job from silently disappearing and gives an operator a precise answer to why an account has not advanced.

How does the design support changes?

Tom separates eligibility, enrichment, scoring, and contact search into modules. A router coordinates them. His team can change one scoring rule or data provider, watch the result, and revert it without rebuilding the rest of the path. Every account should retain its current state, the rule that put it there, and the next time it will be checked.

Tom reports better meeting outcomes during the talk. That is his account of an early result, not a controlled comparison. A team copying the design should measure its own false exclusions, enrichment spend, and accepted meetings before claiming an improvement.

Watch and read more

Watch the speaker recording. Deepline's GTM systems guide provides more context on the role.

Frequently asked questions

What is an outbound state machine?

It is a process in which each account has a state, a transition rule, and a visible reason for a hold or next action.

Why check CRM exclusions before enrichment?

The check can stop outreach to customers, open deals, and do-not-contact records before the team pays for more data.

Full working transcript

The transcript is collapsed by default. Expand it to read the talk. Private names are omitted where needed.

Show full transcript

This is a working transcript with chapter timestamps. It may contain transcription errors. Private names have been removed; check the video before quoting a precise phrase.

00:00:00 Why outbound needs states

00:00:00 Hey, everybody. I'm Tom Bonagura. I am the GTM engineer over at Air.inc. Today, I'm going to be talking about how I engineered Air.inc's outbound motion as a state machine. And why I think everybody should build their GTM systems as modular code. So let's start off with where we've been. Obviously, everybody has no code tables. They're using Zapier, Play, et cetera. And everything is stitched together by hand and hope. Signal providers are stitched together by columns or different nodes that you're just hoping are going to work together.

00:00:29 One small change can break three other things in your system. And a failed step often fails silently and doesn't even give you a Slack notification. And it worked. I built like this for a while. I wasn't very proud of it. But it wasn't modular and it wasn't built to tell me when something broke. So I had a hypothesis. I think outbound should run like a state machine. I wanted to treat outbound like a real engineering project. And I also stitched it together with no code tools and a frayer.

00:01:05 And my company took a bet and I took a bet that the numbers would follow. Our meeting booking rate, so accounts assigned to meeting book percentage, went from 1% to over 6%. It's actually higher than 7% as of today. Everything after this slide is how I tested this hypothesis. But I do want to give a quick caveat. Obviously, I'm giving credit to my reps too. And I usually want to calibrate strong scoring weights against our win-loss history. So first, what is a state machine?

00:01:41 Think about ordering a package. When a package is ordered, it can either be ordered, placed, shipped, or eventually delivered. A package is always in exactly one state. It can never be in two or it can never be in one.

00:01:52 Account states and holds

00:01:52 If something goes wrong, it also doesn't vanish. It gets held with a reason. So you get notified that it's delayed and it constantly rechecks. And that's the whole idea of a state machine. Now, picture treating every account for your pipeline the exact same way. How it started was I wanted to act like a systems architect. So I took my team from Toronto. We locked ourselves in a basement for about five hours and did a giant whiteboard session. And what we did, we were mapping out every schema we wanted in our tables,

00:02:19 how we wanted the pipeline to work with the scraper, deduping, and the scoring logic. And then I turned that into a pretty flawed artifact. And I let Claude and Deepline's infrastructure take it from there. Enter Deepline. Shout out to Jai and Anand. They named it after what I was calling my outbound engine. And essentially what this scale does is I pointed it at my ginormous spec. And pretty much one shot, 80% of the database structure, and 50% of our wired states in the first pass. I was very happy with that.

00:02:51 Essentially what it does is it builds it as plays in Deepline, creates the Postgres tables, wires them through one centralized orchestration plate, and it makes them all modular, swappable, and versioned. Now let's take a little look inside of what I actually built. So every block here is technically both a state itself and the script underneath it that's transitioning the states.

00:03:15 Enrichment and rechecks

00:03:16 So obviously you have your web visitors, recent hires, web scraping, and any other signals that you want, and they go into the state of domain in. Then they become queued. Next, what happens in the queued state is we have a script running that checks against our CRM's block list, so we don't want to hit up customers, open deals, do not contact anything before we're actually spending money. So we put them into a held state, or they move on from there. So we put them into a held state, or they move on to triaging.

00:03:43 Next, we have our triaging. This is basic firmographic signals to automatically dequeue anyone that we don't want to spend our reps' time outbounding to. If they fail the triage, every 60 days we do a re-enrichment, so that companies that were too small for us to outreach to initially, we can re-put them into the system. Now if they move past the triage because the firmographic data was good enough for us, they move into the scoring state. What this is is a series of plays, each hitting its own provider to get us ad spend, tech stats,

00:04:12 funding, and all the other good signals that you want. There's around 10 in total. And then we have one giant scoring algorithm play that takes all this data and pumps them and tiers them. Next, they move into the official scored state. And from here, they attempt to find contacts, or contacts get exhausted, meaning they didn't find enough. And then that gets rechecked every 60 days, hoping for a good hire at one of these companies that has the underlying data that we really want to outreach to.

00:04:41 Now, you might be asking, why a state machine? Essentially, there's explicit gates and there's no hidden flags. With play-like tools, there's APR-like tools, a flag in a cell or an error often just stops that row, or honestly, most people don't even have Slack notifications or any type of notifications to let them know that something went wrong. That's a state machine. You have a name state and there's a reason attached. So essentially, how this works is, I know every single error and every single reason why a company didn't even move on

00:05:13 to the scoring state. In addition, one change breaks three other things. In my state machine, in my modular, the way I built it, if you swap one state, nothing else gets broken. So if I want to completely change my scoring algorithm, the triaging and contact finding, everything else stays perfectly intact. Also lastly, a stock record is easy to forget about,

00:05:35 Results and iteration

00:05:37 especially in row or spreadsheet type of tools. A state machine is held and rechecked automatically based on the way that you built it. Building it as a state machine means that every branch is something you can point to and explain, and there's no hidden Salesforce field errors or play cells that stop working. Lastly, I'm going to talk a little bit about modularity. I think this is arguably the most important part to building a code engine for doing outbound or any other GTM tools. I have one router that acts like the orchestration play

00:06:09 that calls the rest. Essentially, that can call the eligibility check, which is the Salesforce gate, the triage, full scoring, contact search. That's the whole reason that this is code and not a no-code canvas. I can ship a new version of any stage straight to live traffic, and if it's wrong, I'm going to get reverted back instantly. I think the whole theory behind how I like to run GTM, especially GTM engineering, is that you make the most progress when you do fast iterations and slow final decisions.

00:06:38 If you're trying to do a lot of stuff, you're going to have a lot of work. You're going to have a lot of data and time. You're going to have a lot of resources that you need to be able to use. You're going to have a lot of code that you need to be able to do in a really fast way. And that's what a modular engine is. You can try this on a drag-drop workflow. There's no diffs, there's no rollbacks, and there's no way back to yesterday.

00:06:46 So you always have trouble going back to the state that was working perfectly. But in this way, you will avoid that. Now, where it stands today is it's modular, it's still growing. I'm pretty confident in our input system, enrichment, scoring, prospecting. But what keeps changing is everything upstream of it. I can constantly add new vendors, a new signal, it's as easy as getting an API key and doing a nice little prompt. It has all the context of every other state that I have in my system,

00:07:14 and almost perfectly writes up to it every single time. And that's all. Any questions? Are you versioning also the sequences? Sequences in regards to? I mean, I'm assuming that once you've enriched everything, you're pushing this to a sequence, some kind of average sequence. Are you somehow versioning the steps in this sequence, the copy? How did you get from 1% to 6%?

00:07:49 Is it just in your enrichment, or is it like I've got everything I've lost on the way, or did you actually optimize the copy and the sequence and the structure of the sequences? Yeah, I think that's a great question.

00:08:00 Questions

00:08:00 Not to pat myself on the back too much here, but we have the same exact team, same exact list sizes that I'm handing out, and we have not changed the copy once since increasing that from 1% to 6%. I think our scoring algorithm definitely helped out a lot with that, in addition to being able to, I think the main benefit of this is iterating so fast, right? I could change different providers that I want, like one big one I've been using is Ad Intel, and essentially that helps me with ad spend and things like that.

00:08:29 But I tried different versions of the data that it gave me, right? So initially I was looking at how many assets that somebody else can build their app, right? But now I look specifically for things like, what was it six months ago versus today? And I was able to test that really fast and keep pushing it, let it run live for a week, and then revert, if anything, lower 9%. I was just going to ask if you have any deterministic or other plays around emails, like how are you evaluating each step of each module?

00:08:57 Yeah, I will be completely honest, it is slightly naive in that way. I am like, I was a software engineer, but I was a junior, which I will say very confidently because I think my skills lie in GTM mainly. I'm very results-driven, and I think especially in like, I'm the only GTM engineer and I have an intern with me. We're very much like move fast, test it, and if anything goes wrong within that week period, we'll revert back. We have time for one more. Anyone else?

00:09:30 Cool. No, we're all good. Thanks. Thank you.