Writing
Deterministic templates that quote a real line from the prospect's site. If you want a model, point it at Ollama on your own disk. If that model is down or rambles, Otto falls back to the template instead of blocking.
Otto finds businesses worth talking to, reads their website, writes the email, sends it from your own mailbox and drafts the replies. It runs on your machine, in a loop, with nothing to subscribe to.
git clone https://github.com/MuhammadAbuBakar69/otto
Every autonomous agent bills you per token, so every autonomous agent is built to think less. Otto takes the other exit: the reasoning is optional, the plumbing is plain Python, and the whole loop runs on your own hardware.
Deterministic templates that quote a real line from the prospect's site. If you want a model, point it at Ollama on your own disk. If that model is down or rambles, Otto falls back to the template instead of blocking.
What happens next is four SQL counts, not a model call: replies, approved drafts, ready prospects, unread sites. Asking a model "what should I do?" spends money to answer a question the database already answered.
A CSV you write, or OpenStreetMap through Overpass. Neither wants a key. Reading a company site is three HTTP requests and some regex, not a scraping subscription.
This is not a video and not a mock. The panels below run a JavaScript port of
pipeline.py, write.py and replies.py, and the
test suite checks that its drafts are byte-identical to the Python ones. Approve
something and watch the cycle pick it up.
Python 3.9 or newer. No install step, because there is nothing to install.
One JSON file holds your pitch, your signature and your postal address. Secrets stay in .env.
python3 -m otto init
A CSV you write, or a city and a category. OpenStreetMap needs no account and no key.
otto discover --source osm
Three requests per company, then structured facts: title, description, headings, contact address.
otto profile --limit 10
Drafts wait in the local outbox. Nothing sends until you press approve, then the loop keeps going.
otto dashboard && otto run
It does not guess an email address, and it does not send to someone who opted out. Missing address means the draft is skipped with a reason, not a bounce.
Cold outreach is only defensible if it is small, honest and easy to stop. These are in the code, not in a blog post.
No sequences, no drip campaigns, no "just bumping this". A prospect gets a second message only after they reply to the first.
The word unsubscribe in a reply sets the prospect to opted out, cancels anything queued, and blocks every future send before it reaches the mail server.
Drafting and sending have separate daily limits. Otto refuses to send at all without a postal address and an opt-out line, because commercial email needs both.
Really free. Otto is MIT licensed and runs locally. There is no hosted tier, no metered API and no account. The only cost that could exist is your own mail provider, and a Gmail app password on a spare address costs nothing.
The trade-off is real: you are the one running it. There is no dashboard in the cloud, no team seats and nobody to call when it breaks.
You get the template engine, which is the default. It quotes a real sentence from the prospect's site and stays under 130 words. It is less varied than a model and more predictable, which is usually the right trade for a first email.
If you do have Ollama running, set OTTO_MODEL_PROVIDER=ollama. Same
code path, local weights, still no bill.
Harvey is the reference implementation and it is good: deterministic decisions, an outbox, a skills library, a local dashboard. It runs on your Claude or Codex subscription, which means a recurring cost and an API-shaped dependency.
Otto keeps the architecture and removes the subscription. Templates instead of prompts by default, Ollama if you want a model, SQLite instead of a service, and the Python standard library instead of a requirements file.
Not by itself, but it can if you point it at a list of five thousand addresses. Otto is built for tens of prospects, on a spare domain, with a human reading every message before it leaves. That is the version of this that works.
Warm a new mailbox before you send from it, keep volumes low, and honour opt-outs the same day. The repo documents the settings that matter.
Yes, and people already do: recruiting, partnership intros, asking local
businesses about a service they clearly need. The pipeline does not care what the
product is, only that the pitch lives in otto.json.
31 Python tests with no network access, covering the decision order, the template, the reply classifier, opt-outs, caps and the delivery gate. Plus 9 JavaScript tests for this site, one of which asserts that the browser drafts match the Python drafts byte for byte.
CI runs the Python suite on 3.9 through 3.13, lints with ruff, builds the Docker image, and deploys this page.
The demo seeds a throwaway database so you can see the whole loop, outbox included, before you point it at anything real.
python3 -m otto demo