Open source · MIT · no API keys

The outreach agent with a $0 model bill.

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
Zero dependencies Dry run by default Nothing sends without you
Why it is free

Harvey proved the shape. This one drops the subscription.

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.

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.

per email$0.00

Deciding

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.

per decision$0.00

Finding and reading

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.

per prospect$0.00
Live demo

The real logic, running in this tab.

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.

127.0.0.1:5555
dry_run
otto ▸ outbox
prospects0
to profile0
drafts0
approved0
decisionidle
Note This demo never leaves your browser: delivery is a dry run and nothing is sent. Fetching a real website needs the CLI, because browsers block cross-origin requests. The two prospects marked not read yet have a captured snapshot standing in for that step.
How it works

Four commands, then it runs itself.

Python 3.9 or newer. No install step, because there is nothing to install.

01

Name your product

One JSON file holds your pitch, your signature and your postal address. Secrets stay in .env.

python3 -m otto init
02

Find prospects

A CSV you write, or a city and a category. OpenStreetMap needs no account and no key.

otto discover --source osm
03

Let it profile

Three requests per company, then structured facts: title, description, headings, contact address.

otto profile --limit 10
04

Approve and run

Drafts wait in the local outbox. Nothing sends until you press approve, then the loop keeps going.

otto dashboard && otto run

The decision order

pipeline.decide()
  1. 01 handle_repliesA reply is waiting. Opt-outs jump the queue. replies
  2. 02 sendYou approved something. It goes now, inside the daily cap. outbox
  3. 03 writeA profiled prospect has no draft yet. writer
  4. 04 profileA prospect exists but its site has not been read. reader
  5. 05 idleNothing to do, so it sleeps instead of spending. sleep
$ python3 -m otto run
==============================================================
Otto is online. Model: none (built-in templates).
Delivery: dry_run.
==============================================================
 
[09:15:00] cycle
  handler  -> 1 answer drafted for approval
  outbox   -> 0 sent, 0 skipped (20/day cap)
  writer   -> 3 drafts waiting for you
  reader   -> 5 sites read, $0
  decision -> handle_replies
  sleeping 900s. Ctrl-C to stop.

Two things it never does

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.

Guardrails

Boring on purpose.

Cold outreach is only defensible if it is small, honest and easy to stop. These are in the code, not in a blog post.

One email, one ask

No sequences, no drip campaigns, no "just bumping this". A prospect gets a second message only after they reply to the first.

Opt-outs are permanent

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.

Daily caps, and a legal footer

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.

Questions

The things you would ask next.

Is it really free, or free until a limit?

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.

What happens without a language model?

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.

How is this different from Harvey?

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.

Will this get my domain blacklisted?

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.

Can I use it for something other than sales?

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.

What is actually tested?

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.

Start with six invented businesses.

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