Hustler Engine
AI-assisted brand discovery and cold outreach with the entire pipeline running on the device in your pocket — in 2022.
Built across three runtimes at once — an iOS and macOS client, a Next.js web surface and a Node backend — with a lead pipeline that fuses six data sources and three separate model passes into a single record, all orchestrated on the device.
Doing AI product work before it was a category
Hustler Engine was a lead-generation and cold-outreach platform for people looking for brand partnerships — and its interesting property is the date. This was built through 2022 and early 2023, when using a language model as a structured extractor and a scorer rather than a chat box was not yet the obvious move.
The client was iOS and macOS in SwiftUI with Combine, roughly 18,000 lines across 123 files in layered MVVM. Behind it, a Next.js web surface and a Node.js backend — REST API design, JWT auth, the PostgreSQL schema, and the model-driven email personalisation.
spur-i-t.com · Minsk Sorted industries advertising, apps +29 · revenue under $1M · monthly vis. under 10k · employees 50-200
rubyroidlabs.com · Minsk Sorted industries advertising, back-end · revenue $1M–$10M · monthly vis. under 10k · employees 10-50
lmedia.by · Minsk Sorted industries advertising, communicat… · revenue under $1M · monthly vis. under 10k · employees 1-10
- Returned
- 12 brands
- Fields per brand
- 24
- Source
- Companies API + Places
02 — Discovery
Five filters, twenty-four fields back.
The generate screen is a filter set over a company database — locations, industries, revenue band, monthly visitor band and headcount band, the last three as fixed enums (under-10k, $1M–$10M, 50-200) rather than free numbers, because that is how the source data is bucketed.
Each result comes back as a full record: domain, city and country, industry list and primary industry, revenue, monthly visitors, headcount, social profiles, technologies, year founded, phone. Everything the outreach step later needs to write a sentence that sounds like it was written by someone who looked.
- Five filter dimensions, three of them bucketed enums
- One record carries 24 fields — not just a name and a domain
- Social profiles resolved so the brand can be verified, not guessed
- Results sortable and selectable before anything is written
drafted from description + rating + followersReady
queued — throttled to one call per secondWriting
- Throttle
- 1 req / s
- Cached
- Core Data
- Per brand
- one draft
03 — Generation
Fifty-seven drafts, one at a time.
Once brands are selected, the engine writes a personalised email for each one from what enrichment found — the description, the Google rating and review count, the Instagram following — measured against the user’s own company profile.
The rate limit is the interesting part. Model calls had to be throttled to roughly one per second, and the codebase ended up with three separate limiters built on three different primitives — a reactive one on a DispatchQueue, a thread-safe one using NSLock for an atomic test-and-set, and a blocking one on a DispatchSemaphore.
- Progress is per-brand and resumable, not a spinner
- Drafts cached in Core Data so a re-open costs nothing
- Three interchangeable throttles, one per concurrency primitive
- Fit scoring and copy come from separate passes
picked first — ranked above info@ and hello@ Primary
generic inbox — ranked lower Fallback
UID-based, sorted by received date Synced
remote image downloaded, attached inline Sent
- Prefix rules
- 22
- Protocols
- SMTP + IMAP
- Credentials
- user’s own
04 — Outreach
A mail client inside the app.
There is no sending service behind this. The app holds the user’s own SMTP and IMAP credentials and talks to their mailbox directly through MailCore2 — fetching inbox and sent mail by UID, and sending plain, HTML, attachment and inline-signature variants, including downloading a remote signature image and attaching it properly.
And a small piece of judgement that mattered more than it looks: when scraping turns up several addresses for one company, a 22-prefix ranking decides which one a human should actually receive it at.
- User’s own mailbox — no sending infrastructure to run
- IMAP inbox and sent sync, UID-based
- Four send paths including true inline signatures
- 22-prefix heuristic picks the right recipient
Three rate limiters, on purpose
Model calls had to be throttled, and the codebase ended up with three different limiters built on three different concurrency primitives — one reactive on a DispatchQueue with a subject, one thread-safe with NSLock doing an atomic test-and-set, and one blocking on a DispatchSemaphore.
Underneath sits a generic Combine networking layer: publisher-returning requests, a custom operator for response validation, typed error mapping that separates unauthorised from server from decoding failures, and connection pooling tuned per endpoint — the email-search path gets a much higher concurrent limit than the rest.
Stack
Client
- SwiftUI · UIKit bridges
- Combine
- Layered MVVM
- Core Data
- Firebase Auth · Firestore
Web & API
- Next.js
- Node.js
- REST architecture
- JWT auth
- PostgreSQL
AI & data
- OpenAI API
- Structured JSON extraction
- Google Places · Photos
- Instagram profile data
- MailCore2
- SMTP · IMAP
- Template engine
- Inline signatures
Outcome
Ran with 100 beta users. More importantly it is the direct ancestor of Rawly — the same problem (turn a sales motion into software) attacked three years earlier with hand-rolled concurrency instead of a queue, and on a phone instead of a worker fleet.
Same problem, three years earlier, one toolchain harder.