Howdy Folks,
When I relaunched this newsletter I promised an AI pillar where my own company is the subject. I was going to write it as a tour of everything we run, a paragraph each. I changed my mind. One build per issue, all the way down, and rather than describe it, I am going to hand you the setup.
This month: the machine we built after we left Salesforce.
I am going to call it a GTM machine and not a CRM, because the CRM is the least interesting part. A CRM stores what your reps typed. This thing decides who we should be talking to, finds them, writes to them, reads what comes back, and hands a rep a drafted reply with the context attached. There is a database in there somewhere. Nobody logs into it for fun.
Thesis, up front: with AI coding, the software is now the cheap part of a go-to-market machine. The expensive parts are the definitions, the list, and the mailboxes. Get those three right and the code follows in weeks. Get them wrong and you have built a very fast way to email the wrong people.
Here is the whole thing. Steal what is useful.
1. Why we left, and what the new one costs
We ran sales on Salesforce. The reason we left was not that Salesforce is bad. It was that a system of record was never the thing I wanted. I wanted a system that does the work and asks a person to approve it.
On April 14, 2026 the new repository got its first commit: a web app scaffold wired to a Postgres database and the Claude API. The second commit that day was the schema, sixteen tables. On April 16 the first slice worked end to end: a prospect replies to a cold email, a model reads the reply, the account is assigned to a rep, a drafted response lands in that rep's inbox. On April 17 we imported the old book from an Excel export called, and I promise I did not make this up, CRM_Export.xlsx.
Here is what the replacement runs on, at published list prices read from each vendor's own pricing page on September 21, 2026. This is not my invoice. Usage charges sit on top of nearly every line.
| Layer | What it does for us | Published list price |
|---|---|---|
| Next.js on Vercel Pro | The app reps use, the API, and 15 scheduled jobs | $20 / seat / month |
| Supabase Pro | Postgres, the single source of truth | $25 / month |
| Claude API, Opus 4.6 and Opus 5 | Drafting emails, judgment calls, the in-app assistant | $5 in / $25 out per million tokens (both) |
| Claude API, Haiku 4.5 | Reading and classifying every reply | $1 in / $5 out per million tokens |
| SmartLead | Sending the sequences, catching replies | $39 to $379 / month by plan |
| Google Workspace | The 39 sending inboxes | $7 / user / month (Starter, annual plan; $8.40 on flexible billing) |
| Apollo | Decision makers at prospect companies | We run under a 5,000 credit / month cap |
| LeadMagic | Finding and verifying emails on the big list | $99 to $849 / month by plan |
| Clay | Building the big list, once, in August | $185 / month Launch, $495 / month Growth |
Sources: vercel.com/pricing, supabase.com/pricing, claude.com/pricing, smartlead.ai/pricing, workspace.google.com/pricing, leadmagic.io/pricing and clay.com/pricing, all read September 21, 2026. SmartLead and Clay prices are monthly billing. The Apollo cap is our plan's monthly credit limit.
For scale: Salesforce lists Sales Cloud at $25 to $550 per user per month depending on edition, billed annually above the entry tier (salesforce.com/sales/pricing, September 21, 2026). For three reps that is anywhere from $75 to $1,650 a month at list, before add-ons. My arithmetic.
The line that surprised me was not the model. It was the mailboxes. Thirty-nine inboxes at the Workspace Starter list price is $273 a month on the annual plan, or $327.60 on flexible billing, before a single email is written. The sending infrastructure costs more at list than the database and the hosting combined.
2. Step one: define the customer from your own book
Every ICP document I have ever been sent by a vendor starts with a persona named Logistics Lucy. Ours started with a spreadsheet of who actually pays us.
In August we pulled every customer with employee data on file, 434 companies, and looked at the distribution (our TAM run log, August 22, 2026):
| Percentile | Employees |
|---|---|
| Median | 9 |
| 75th | 27 |
| 90th | 120 |
| 95th | 303 |
| 99th | 1,376 |
| Largest customer we have ever had | 3,382 |
Our median customer has nine-twenty seven employees (this is likely lower than reality because we are relying on apollo.io to list company size and they underestimate size for ecommerce brands drastically- nonetheless we used that as the filter to normalize). That one number did more for the ICP than any workshop. It set the size cap at 5,000, which drops the Nike-and-Costco-shaped rows from every list, and it forced a floor of 2 employees instead of the 10 we had planned, because a floor of 10 would have excluded accounts that would actually be perfect fits due to Apollo's under reporting head count.
It also split the motion in two, at roughly 250 employees. Above that line there is a CIO or an IT director, the pitch is "keep every carrier contract you have and see it beside our brokered rates in one quote," and the cycle runs through a security review. Below it there is an owner or an ops manager who is quoting by phone and email and has never run a TMS. A 30-person fabricator does not have a CIO. Same product, opposite message, and they never share a sequence.
On top of that sit the hard filters, all of which a company has to pass: US or Canada, $5 million to $500 million in revenue, 5 to 10,000 employees, ships physical goods on pallets, and is not a 3PL, broker, carrier, forwarder, warehouse operator, parcel courier, parcel-only retailer, or a services business. Then six verticals, each with its own freight profile and its own opener: retail, agriculture, wholesale distribution, auto parts, heavy manufacturing, and consumer goods sold through retailers. Plus one persona that cuts across all six, the technology buyer, who gets a separate campaign because they feel different pain: every new carrier is an integration project.
And one thesis that every single email carries, in every vertical: your direct carrier tariffs and our brokerage rates pricing the same shipment side by side, inside one TMS. Pure brokerage shops make you ship on their rates. Pure TMS platforms lock brokerage out. We are the place you get both. If you have read Price vs. Cost vs. Value, that is the same argument pointed outward.
Here is the part Claude did. All of that lives in the codebase as 18 markdown files: the company, the offering, the positioning, the objections, the voice, examples of good and bad drafts, and one file per vertical. Half of them came straight out of a focused interview with me. Seventeen are compiled into the app at build time, and the reply classifiers and every drafter read them. When I want every cold email we send to say something different next week, I edit a text file and push. That is the entire content management system.
3. Step two: build the list, and do the boring part first
"Ships LTL" is not a checkbox in any database. Not Clay, not Apollo, not anywhere. So a freight TAM is built in two stages: cast a wide firmographic net using industry, size and footprint as a proxy for freight, then prove it with signals and score. Skip the second stage and you get 20,000 rows and a reply rate you cannot explain to anyone.
Before any of that, the suppression list. This is the step everyone skips and it is the one that would have hurt us most.
We pulled all 12,021 companies out of the CRM on August 22 and built a block list. Then we pulled the live customer roster out of the TMS, which is the actual system of record, and found 434 active customers, of which 363 were not in the CRM at all. The CRM covered about 16% of our real customer base. Cold outbound built on CRM suppression alone would have left roughly 370 live customers unprotected (our Stage 0 report, August 22, 2026). The final block list ran to 7,724 domains, plus 90 accounts with no resolvable domain, matched on name.
If you take one thing from this section: your CRM is not your customer list. Your billing system is.
Then the sourcing, in Clay. Claude wrote every query and kept the run log, and the log is where the actual knowledge is:
- Get the real taxonomy. Feeding Clay an invalid industry value makes it return the valid list. That is how we got its 457-industry taxonomy, from which we picked 72 physical-goods industries and grouped them into 13 clusters, split across 27 Clay tables: furniture, building products, industrial machinery, metals, plastics, chemicals, packaging, food and beverage, auto parts, medical equipment, wholesale distribution, and so on.
- Generic industries poison small-company queries. "Furniture" plus "Retail," "Consumer Goods" and "Design" returned 291,184 rows of design blogs and skincare brands. "Furniture" alone returned 7,171, and it was furniture. At 2 to 200 employees, only specific manufacturing and wholesale industries survive.
- Clay truncates at 50,000 rows per table on our plan, silently. The warning is small grey text on a dropdown. Read the count before you save, and split any cluster that is over.
- Select no enrichments while sourcing. Every table was created with nothing enriched. Sourcing the entire 348,759-company list cost zero enrichment credits. The only credits this project spent went to our own customers, resolving domains for the block list.
The funnel from raw pull to clean list, all from the August 23 handoff:
| Stage | Companies |
|---|---|
| Rows Clay returned across 27 tables | 537,359 |
| Parked: no domain (recoverable later, costs credits) | 144,068 |
| Collapsed: duplicate rows for the same domain | 31,781 |
| Unique companies | 361,510 |
| Removed: on the block list (customers, open deals, prospects already in a sequence) | 2,011 |
| Removed: customers with no domain on file, matched by name | 5 |
| Removed: over 5,000 employees | 336 |
| Removed: headquartered outside US and Canada | 9,635 |
| Removed: already in the CRM, not yet worked | 764 |
| Clean, contactable | 348,759 |
The dedupe and suppression ran in a local script rather than in Clay, because Clay cannot dedupe across tables and the overlap runs to thousands (15,756 companies showed up in more than one table). On August 23 the list went into the CRM in one import: nine of the 348,759 were already there, so 348,750 new companies, and the companies table went from about 12,000 rows to 360,826.
One honest note on the shape of it. 195,135 of those companies, 56%, have 2 to 10 employees. That is genuinely in-ICP, given the median-nine customer, and it is also where the junk concentrates. The tighter cut, at 11 employees and up, is 153,624 companies.
4. Step three: find the people, at the rate you can send
The instinct with a 348,759-company list is to enrich it. Three or four contacts per company is over a million lookups. We did not do that, and I would argue you should not either.
Contact data decays. Our working assumption, written into the pull request that built this, is fast enough that buying a million contacts in August means a meaningful share are stale before you ever write to them. So the machine enriches in a daily drip instead: 25 companies a day, biggest first, one best contact per persona (executive, operations, technology, finance) found through LeadMagic, each email verified, and the company marked to be re-checked in 90 days. Every contact that reaches a sequence was found and verified within days of the first touch, not months.
Alongside it, a separate daily pull takes about 55 companies a day from Apollo across the six verticals and finds up to three operations decision makers and one technology contact at each, under the 5,000 credit cap.
A warning for anyone selling into freight: your buyer is not email-native. Our planning assumption is 50% to 65% email coverage on the logistics persona at companies under 500 employees, not the 85% to 90% you would see selling software. A traffic manager at a 300-person plant may have no LinkedIn profile and an address no waterfall will find. That persona answers the phone. Budget for it.
5. Step four: the mailboxes
This is the part I would have paid someone to explain to me in April.
Cold email at volume does not go out from your company domain. It goes out from a separate estate of domains and inboxes that can absorb the reputation risk, warmed up slowly, capped hard. Ours, from our provisioning plan and the August 24 status check:
- 13 domains, registered August 22 after checking availability against the registry, on Google Workspace with the full mail records on each: MX, SPF, DKIM, and a per-domain DMARC policy.
- 3 inboxes per domain, 39 total, because two to three per domain is the band current deliverability guidance converges on.
- 25 sends per inbox per day, enforced in SmartLead, which rotates senders across the estate.
- 14 days of warm-up before an inbox goes live. Our sync job tracks every domain as warming and promotes it on its birthday.
- Every inbox is named for a real rep. Three sales reps, no exceptions, and I am deliberately not one of them. Every cold identity maps to a person in the reply rotation, so a prospect always hears back from the name they wrote to.
SmartLead runs 14 campaigns: six verticals times an A and B variant, plus the technology persona times A and B. Sends go out Chicago time, 6am to 6pm, weekdays.
The sequence is four touches on days 0, 3, 7 and 12. Touch one is identical across every variant and carries two slots that Opus fills per lead from the knowledge files: a personalized opener and the vertical's value proposition. Holding touch one constant means any difference in reply rate belongs to the variant, not the personalization. Variant A tells the one-platform story: five carrier portals, five invoice flows, a different claims process per carrier, replaced by one system that wraps around the stack you already run. Variant B is the show-me story: every email ends in a concrete, no-meeting offer, usually "send us 90 days of LTL data and we will rerun it lane by lane against our blended model." Touch three opens a new thread. Touch four is the breakup. Every claim in the copy traces to one of four pages on our own website, and nothing was carried over from the previous generation of campaigns.
Now the arithmetic falls out on its own:
| Sending math | |
|---|---|
| Inboxes live | 39 |
| Sends per inbox per day | 25 |
| Sends per day | 975 |
| Touches per sequence | 4 |
| New people entering a sequence per day | 243 |
The CRM computes that number from the inboxes that are actually live and queues only that many contacts each day, in three runs. Buy three more inboxes and it rises by 19 a day. Lose a domain and it falls. There is never a growing backlog of people waiting to be emailed, and there is never a day the estate sends more than it can carry.
6. Step five: what happens when someone replies
Every reply hits a webhook. Haiku reads it and puts it in one of seven buckets: HOT, WARM, COOL, DECLINED, UNSUB, HOSTILE, or WRONG_PERSON. An unsubscribe, a hostile reply, or a wrong-person reply disqualifies that one contact and only that contact. The company stays.
Then the rule that took us the longest to get right: a reply is not a deal. A reply is an engagement. It moves the company up a fixed ladder: contacted, warmed, engaged, qualified, opportunity, customer. The ladder never steps backward. A deal opens only when something real happens: a decision maker asks for a meeting, a decision maker asks for rates, someone books a call on the website, a rep records that shipping data came in, or a rep opens one on purpose with a reason. Everything else is a conversation.
That conversation lives in an Inbox inside the CRM, with a queue on every thread: Needs reply, Drafts ready, Waiting, Snoozed, Handled. Opus has already drafted the response with the whole history attached. The rep reads it, edits it, or types a one-line instruction and gets a revision, then sends.
To be precise about what is automated and what is not: the cold sequences from the rep-named inboxes send on their schedule without anyone clicking. Every reply, and anything that leaves a rep's real mailbox, needs a rep to approve it.
The machine also watches. When a company already in our pipeline visits rocketshipping.io, Slack lights up with the rep's name on it. Gmail syncs every 15 minutes. A nightly job pulls booked revenue from our freight reporting system so each rep's actuals sit next to their pipeline. And as of September 10, every rep can get their own AI agent that acts as them in the CRM through 53 actions over an API, with a key per rep, a default cap of 200 sends a day per key, and one switch that turns every agent off. That agent is the one exception to the approval rule above: it can send as its rep without the rep clicking approve, which is why the cap and the switch exist.
7. The sales rep is in the codebase
This is the part I care about most, and the part I would push back on if you told me it could not work at your company.
The main branch of this repository has 176 commits as of September 21. Claude Code wrote the code. I wrote what it should do, argued with it about what a deal is, and read the diffs. And Carly, one of our three sales reps, has nine pull requests and seven commits on that branch (GitHub, read September 21, 2026). She is not a developer.
What she shipped is the stuff that only the person in the seat can see:
- A daily job was creating the same task every morning, so the queue filled with duplicates. She found it, and when the cleanup came up she gave the instruction that is quoted in the pull request: archive them, don't delete them, because "cancelled" already means a rep decided against something and she did not want 2,813 machine duplicates poisoning that signal. So 2,813 tasks were archived, none deleted, and the queue regrouped by account.
- Company names showing as "Unknown," search that did not find things, and a new-deal flow that did not exist. Three fixes, one pull request, no database changes.
- The check that keeps a current customer out of a cold sequence. Her first gate is live. The stricter version, placed at the one point every push goes through, failing closed, and matching on company name as well as domain because customers do not all have a domain on file, is in review as I write this.
The mechanism is simple and it is the whole point. She describes the bug from the seat that feels it. Claude Code writes the change. It goes up as a pull request, gets reviewed, and merges. The person who feels the bug files the fix, and she never had to learn what a migration is.
The safety net that makes this responsible is the review, not the person. Before the Inbox build merged on September 7, six reviewers and a skeptic per finding went over it and confirmed 21 defects in the first cut, including a draft that could be sent twice. All fixed before merge. That gate is what lets a sales rep ship code and lets me sleep.
8. What it cost us
I promised the failures come attached, so, briefly. Building your own means there is no vendor to call, and every one of these ran silently for days while the dashboards looked fine.
- Eight days of stranded replies, April 29 to May 6. The script that created the first 14 campaigns never subscribed them to the reply webhook. Open rates were 50%, replies were zero, and nobody was watching the ratio.
- Nineteen days of skipped pushes, August 23 to September 11. A second generation of campaigns was registered, a lookup that expected one row got two, and every push was skipped. That landed on top of an older bug that had August pushing about two contacts a day against roughly 12,000 eligible.
- 289 phantom deals. The open-deal lookup excluded a stage called
closed_won, which did not exist in our database, so the query failed, the error was discarded, and every reply opened a brand-new deal. Up to nine on one company.
All three were caught, all three were small fixes, and all three came from the same root: an error read as an answer. The rule we run now is dull and it works. Every database call checks its error, and anything that sends email fails closed.
That is the price of no vendor. I would pay it again. I just want you to know it is there.
9. I could be wrong because...
Standing feature. Here is where this take falls apart.
- I showed you list prices and activity, not the bill or the revenue. The API spend is not in this issue and neither is a single pipeline dollar. A funnel of 348,759 companies is a cost, not a result, until someone books freight off it.
- n equals one company, with an owner who codes. "The software is the cheap part" is a very different sentence if you have to hire the person who reads the diffs. At most brokerages that person is the entire cost, and I skipped past it because I am the person.
- An ICP built from your own customers can only find more of what you already have. The median-nine cap is a description of our past, not our ceiling. If the next market looks different, this list will not contain it.
- I am grading my own homework. Every failure in section 8 is one we caught. The interesting number is the one I cannot give you.
What's next
Next week the rotation turns to thought leadership: the fuel table is the real negotiation, and I am going to argue it properly. Next month's AI issue is another single build, all the way down.
See ya next week.
Gabe
If you have built your own sales machine, or bolted one onto a bought CRM: what did you rip out, and what did you put in its place? Reply and tell me. I will run the best answers in a future edition, credited or anonymous, your call.