Case study · Client work · Outbound

Outbound signal engine

Built for a company selling outsourced bookkeeping to property managers in the US. It finds firms with a live need, drafts the outreach, and sends nothing until a person approves it. One Rust service, rewritten off a no-code platform that had run out of room.

RustSQLiteHubSpotLLM draftingChrome extension
2signal engines, one pipeline
0messages sent without human approval
1binary serving the app, the API, and the jobs
Everypaid API call logged with its cost

Problem

Nobody searches for the service. They search for the alternative.

The client sells outsourced bookkeeping and back-office work to residential property management companies. Their buyer almost never searches for "outsourced bookkeeping". What that buyer does instead is post a job ad for an in-house bookkeeper, or complain in an industry forum about being buried in reconciliations.

Both are the same moment: this company has the work and is about to solve it the expensive way. That moment is short, and no CRM report will ever surface it, because it happens entirely outside the CRM.

Two engines, one pipeline

Two signal sources, job boards and communities, feed one qualification step, then a human approval gate, then the CRM pipeline SIGNAL Job boardshiring a bookkeeper= has the work Communitiesdescribing the painin their own words Qualifyfit rules, then a draftin the right channel ApprovalA PERSON READS ITedit, approve, or bin Public replyhelpful, in the thread Direct messagewhere the platform allows Emailaddress found and verified One CRM pipelineboth engines tagged by source, so you can tell them apart later
The channel is decided by the signal type, not by the sender's mood. A community post gets a helpful public reply. A job ad gets an email. That map is written down and enforced in code.

Job board engine

Scans for property management companies advertising for a bookkeeper or accountant. The ad is the signal: they have enough work to justify a salary, and they are about to commit to the expensive option.

Community engine

Watches industry forums and groups for operators describing the problem themselves. These are worked as a public, genuinely useful reply rather than a pitch, because that is what those spaces tolerate.

The rule the whole thing is built around

Nothing sends itself.

Every public reply and every email is drafted by the system and approved by a person before it goes anywhere. No auto-posting, no auto-sending, no exceptions in the code for the cases where it would be convenient.

That is a product decision, not a timidity one. The community engine posts under a real person's name in a space where a bad automated reply is remembered for years. The value of being welcome in that forum is worth more than the hours saved by skipping review.

The three message types and which signal gets which one are documented in the repo as their own reference, because that mapping is the thing everyone keeps re-deriving and getting slightly wrong.

Rebuild

Knowing when to leave the no-code platform.

The first version was built entirely on a no-code app platform. It worked, and then it stopped being able to grow. Blocks timed out after two minutes. Its code mode was not standard HTTP, so integrations needed a browser extension acting as a shim. Access to the database and to auth was gated by the platform.

I rewrote it as a single Rust service: server-rendered pages, a small database, scheduled scans, and the external integrations as ordinary HTTP clients. It ships as one container.

The important part is the porting method. The old logic was moved across piece by piece, with tests written against the original behaviour, so the rewrite was checkable rather than a rebuild from memory. That is what makes a migration finishable.

  • Reach for no-code first. Most of these systems die of neglect, not of scale.
  • Leave when the platform's limits start shaping the product rather than the product's needs shaping the build.
  • Port with tests, not from memory.
  • Log the cost of every paid API call, so the running cost is a number rather than a surprise at the end of the month.

What it proves

The best buying signals are outside the CRM.

Intent data you buy tells you a company visited a page. A job ad for the role your service replaces tells you what they are about to spend money on, in their own words, with a date on it. It is public, and almost nobody is systematically reading it.

Building the system to catch that is straightforward. Building it so a person still reads every message before it goes out is what makes it something the client can use for years.

The client is not named here at their preference. Their sector and buyer type are described because the shape of the signal is the point. Prospect data, message copy, pricing, volumes, and credentials are withheld.