How it works
How Optic works
Signals and inbound come in. Each one is scored against the brain of the product it's for. Plays turn the good ones into drafts that open with the product's own read on the prospect's news, and nothing sends until a person approves it. Everything that happens is an event.
Pipeline
One path from a headline to a sent email.
MCP tools, webhooks and the coming web UI call the same service functions, so no surface has logic the agents can't reach.
In
Sources
- SEC EDGAR 8-Ks Built
- News search Built
- Newsroom feeds Built
- IR pages Built
- Inbound forms Building next
Engine
Normalize, enrich, score
Dedupe per workspace, score against the live brain, store the reasons. Built
Play
Draft with the hook
Run the product on their news; the result becomes the first line. Building next
You
Approval inbox
Review from Claude with
list_approvals, then approve, edit or reject. BuiltOut
Send
Inside the send window and caps, from the workspace's own domain. Building next
Events, append-only. Every signal, submission, draft, approval, email, reply, meeting and stage change. They feed the timeline, the daily digest, outgoing webhooks and per-play metrics.
Signals Built
Signals that say why they fired.
Import your accounts and Optic gives each one the sources its brain needs. A job fetches the ones that are due every hour in production.
| Source | What it catches |
|---|---|
| SEC EDGAR | 8-K items 2.02 (earnings), 2.01 (completed M&A), 5.02 (officer or director change) |
| News search | Comms-leader hires, M&A, rebrands, launches, funding, crisis volume |
| Newsroom feed | Anything in the company's own RSS or Atom feed |
| IR page | New article links on an investor-relations listing page |
- Detection is rules, not a model. The company has to be the subject of the headline, and market commentary ("3 reasons to buy…") is skipped.
- The score is
weight × account fit × persona fit × freshness, and every factor is stored with the signal. - One event, one signal. While a signal is fresh, more coverage of the same type for the same account joins it.
- Crisis is volume: a minimum number of outlets in 48 hours with crisis terms in the headline.
- Runs are safe to repeat. One run at a time, capped in minutes, and a source's first run only records a baseline.
Brains Built
A brain per product, in YAML.
The brain holds everything product-specific: who to sell to, how to sound, what to offer, which signals matter and how fast to go. The engine holds none of it.
optic push <workspace>validates the brain and every play, then writes them in one transaction.- A file only gets a new version when its content changes. Drafts and signals record the version they used.
- Validation fails on unknown keys, bad durations or time zones, unknown event types, offers or personas the brain doesn't define, and
autoplays under a brain that requires approval.
# brains/northlight.yaml (a made-up product)
workspace: northlight
icp:
accounts:
tier: target
description: Mid-market fintechs with an in-house comms team
personas:
- id: comms_lead
priority: 1
titles: [Head of Communications, VP Communications]
voice:
tone: Peer to peer. Specific. No hype.
rules:
- Open with their news, never with us.
- Under 90 words, one ask.
banned: [hope this finds you well, circle back]
offers:
- id: read_on_their_news
hook: Our read on how their announcement is landing
cta: Want the full read?
signals:
act_above: 0.7
types:
exec_hire: { weight: 1.0, personas: [comms_lead], fresh_for: 60 days }
m_and_a: { weight: 0.8, fresh_for: 30 days }
funding: { weight: 0.6, fresh_for: 21 days }
pace:
mode: approve
new_contacts_per_day: 10
per_account_per_30_days: 1
send_window: { days: [tue, wed, thu], hours: "09:00-12:00", timezone: recipient }Plays
Plays put the product in the first line.
A play is a trigger, an audience and steps. The analyze step runs your product on the prospect (a read on their coverage, a demo of their app) and its output becomes the draft's hook.
- Plays are validated with the brain Built. Running them, with drafting and sending, is building next.
- New plays start as
status: draft; nothing runs until a play saysactive. exit_onends the play on a reply, a meeting or a bounce. The database enforces the reply rule even if a play forgets it.
# plays/northlight/new-comms-lead.yaml
slug: new-comms-lead
mode: approve
trigger:
type: signal
signal_types: [exec_hire]
min_score: 0.7
audience:
who: signal.person
exclude:
lifecycle: [engaged, meeting, customer, disqualified]
steps:
- enrich: { require: email_valid }
- analyze: { tool: northlight.read, input: { company: "{{company.domain}}" } }
- draft: { offer: read_on_their_news, max_words: 90 }
- send
- wait: 5 days
- draft: { style: bump, max_words: 40 }
- send
exit_on: [email.received, meeting.booked, email.bounced]Approval and sending
Drafts wait for you. Rules don't wait for anyone.
Drafts are messages with status = 'pending_approval'. The approval inbox shows each one with the person, company, play and the signal that triggered it.
Approve from Claude
list_approvals, approve and reject are live Built. approve accepts edits and keeps the original for the edit-rate metric.
Send inside the lines
One sending vendor first, inside each brain's send window and each sender's daily cap Building next.
Replies stop everything
Gmail sync brings replies in Building next; the database then ends the play and cancels queued automated drafts Built.
Events and webhooks
Everything is an event.
Events are named noun.past_tense and carry typed references to the person, company, deal, signal, play and message, so timelines and metrics need no JSON digging.
- The append-only event log and the webhook outbox are in the schema Built. Each delivery is queued in the same transaction as its event.
- Inbound forms post to
POST /v1/inbound/{workspace}/{form}, signed per form Building next. - The outgoing dispatcher retries at 1m, 5m, 30m, 2h, 6h and 24h, then marks a delivery dead Planned.
{
"id": "6f1c…",
"type": "email.received",
"workspace": "northlight",
"occurred_at": "2026-10-18T14:02:11Z",
"data": { "person_id": "…", "subject": "Re: …" }
}Optic-Signature: t=<unix>,v1=<hmac_sha256>; consumers dedupe on id.Vendor seams
Vendors behind interfaces.
The engine codes against interfaces, and each workspace picks its vendor. The first vendors are still being chosen.
| Interface | Does | First vendor |
|---|---|---|
ContactData | Find people at a company, enrich, verify email | Apollo or Prospeo |
Sender | Send a message, check its status | Instantly or Smartlead |
Mailbox | Sync messages with a thread key | Gmail |
Meetings | List meetings, pull action items as tasks | Granola |
Crawler | Fetch a source, return candidate signals | Firecrawl, plus our IR and link crawlers |
Notify | Push an event to a person | Slack or email |
What's built
Where the build is.
The first version is a two-week plan: one engine, two brains and a working Claude interface. The web UI came early.
- Days 1–2BuiltSchema on Postgres, the push CLI with validation, key auth and the first MCP tools, deployed on Cloud Run
- Days 3–4BuiltSignal layer: SEC filings and news search for the starter accounts, deduped and scored, running hourly
- Days 5–6Building nextDrafting with the product hook. The approve, reject, search, get_record, list_plays and play_stats tools are done
- Days 7–8Building nextSending from a fresh domain through one vendor, and Gmail sync for replies
- Day 9Building nextInbound webhook for demo forms and the demo-request play
- Day 10Building nextA second product's brain live, then a review of play performance
- Ahead of planBuiltWeb UI with Google sign-in: records with timelines, plays, the approval inbox and the signal feed
- LaterPlannedGranola sync, CRM sync to Attio or HubSpot, billing
Optic runs our own GTM today.
It isn't for sale yet. If you'd run it for your product, tell us what you sell and which signals matter, and we'll talk when drafting and sending are done.