Build Gojiberry AI

YESreplaces $99/mosaves $1,188/yrback to the verdict

0%0 of 21 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A watcher that polls public signals such as posts, commenters and job changes, scores each person against a written ideal customer profile, enriches only the ones worth contacting, drafts a short note citing the exact signal that fired, and queues it for a human to review and send within safe daily limits.

estimated effort weekendthe files for this build are in the project pack

RuntimeNode 22 with SQLite through better-sqlite3SignalsPublic sources you are permitted to read, polled on a scheduleScoringA written profile turned into deterministic rules, with a model only for the judgement callsEnrichmentOne provider, called only for people who already passed scoringDraftingA model given the specific signal and your own examplesSendingA review queue for a human, with per-day limits enforced in the application

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the watcher, the scorer and the queue.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. decidevaries by source; some are free and some are priced per run

    Why This decision constrains the entire build and determines whether you are allowed to run it at all. Making it after building is the expensive order.

    Get it Choose the source, read its terms of service and any developer policy, and write down what is permitted with the date. If the terms forbid automated collection, choose a different source rather than proceeding quietly.

    Verify A written note names the source, the permitted use and the date checked

  3. API keytypically priced per lookup or in credit bundles; check the current rate and expect a real miss rate on harder contacts

    Why Turns a name into contact details. Without it you have a scored list and no way to reach anyone.

    Get it Create an account with one provider, take an API key, and read its pricing per lookup and its coverage claims. Start with the smallest paid tier rather than an annual commitment.

    Verify A lookup for a person you can verify returns correct details

  4. API keypay per token; drafting a short note is a fraction of a cent

    Why Used for the judgement part of scoring and for drafting. Both are small calls, so the cost here is modest compared with enrichment.

    Get it Create an account with a provider, generate a key, and set a spend limit in the dashboard at the same time.

    Verify A test draft returns text and appears in the provider usage dashboard

  5. have readyfree

    Why Phase 4 depends on them. They do more for draft quality than any prompt instruction, because they carry your actual voice.

    Get it Go through your sent messages and copy the ones that worked. Remove anything identifying about the recipient.

    Verify The examples file contains at least two real messages with recipients anonymised

  6. decidefree

    Why Scoring cannot be built against a vague sense of who is a good fit. Rules need thresholds.

    Get it Write company size, roles, seniority, industries and hard disqualifiers. One page. If you cannot write it, the scoring phase will produce numbers nobody trusts.

    Verify The profile exists as a configuration file with explicit thresholds

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- watches(id, kind, target, cadence_minutes, last_polled_at, enabled) · kind is post_engagement, job_change or keyword
- signals(id, watch_id, person_ref, kind, evidence_text, evidence_url, observed_at) · evidence is what you will quote in the note
- people(id, person_ref, name, headline, company, enriched_at, enrichment_json)
- scores(person_id, signal_id, score, reasons_json, scored_at) · reasons must be human readable
- drafts(id, person_id, signal_id, body, state, approved_at, sent_at, sent_by) · state is drafted, approved, sent, skipped
- send_log(day, channel, count) · enforces the daily cap in the application rather than in your memory

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DB_PATHrequired./data/outreach.dbSQLite file holding watches, signals, people, scores and drafts. This is the backup.
ENRICHMENT_API_KEYsecretrequiredreplace-with-provider-keyYour enrichment provider's dashboard. Priced per lookup, so treat the key as a spending instrument.
MODEL_API_KEYsecretrequiredsk-...Your model provider console. Set a spend limit there as well.
SCORE_THRESHOLDrequired70Minimum score before enrichment is spent. Raising it cuts cost and coverage together.
DAILY_APPROVAL_CAPrequired20How many drafts may be approved per day. Keep it well under platform ceilings, and lower for a new account.
WEEKLY_APPROVAL_CAPrequired100Rolling seven day cap, enforced separately from the daily one. Platform limits vary by account age and acceptance rate, so treat this as an upper bound rather than a target.
ENRICHMENT_CACHE_DAYSrequired90How long an enrichment result stays fresh before a re-lookup is allowed.

The build, in order

  1. One signal source, polled politely

    Real signals land in your database from a source you are permitted to poll.

    1. Write a short note naming the source, what its terms allow and the date you checked. This document is the foundation of everything else.

      Files docs/sources.md

    2. A scheduled poll, exponential backoff on failure, and a unique constraint on person plus signal plus day so re-polling is a no-op.

      Files src/watch.js

    3. The text and the link. A note saying you saw someone engage is generic; a note quoting what they said is not.

      Files src/signals.js

    done when · tick each as it passes
    watch out
    • Deciding the source after building the pipeline is how people end up with a working system they are not allowed to run. Read the terms of the specific source first, write down what it permits, and let that decide the architecture.
  2. Scoring you can argue with

    Each person gets a score and a readable list of reasons for it.

    1. Positive signals, negative signals and hard disqualifiers, in a configuration file rather than in code.

      Files config/icp.json

    2. Rules first. A model call only for what rules cannot express, and require it to return reasons.

      Files src/score.js

    3. Score them yourself, then compare. The disagreements tell you which rule is wrong, which is the only way this improves.

      Files eval/labelled.json

    done when · tick each as it passes
    watch out
    • A single opaque number from a model cannot be debugged, so when it starts surfacing the wrong people you have no lever to pull. Rules produce reasons, reasons produce fixes. Reserve the model for the parts that are genuinely fuzzy, and make it explain itself there too.
  3. Enrichment, spent only where it counts

    You pay for contact data only for people who already passed scoring.

    1. One place in the pipeline where the threshold is applied. Easy to get wrong and expensive when it is.

      Files src/enrich.js

    2. People change jobs, so a cached record has a shelf life. Record when it was fetched and re-enrich after that.

      Files src/enrich.js

    3. Cost per enriched person, and cost per reply once you have replies. The second number is the one that decides whether this works.

      Files src/cost.js

    done when · tick each as it passes
    watch out
    • Enriching every signal before scoring inverts the economics: enrichment is priced per lookup and most signals do not deserve one. A single provider will also miss a meaningful share of people, and a miss is a normal outcome to record rather than an error to retry.
  4. Drafts that quote the signal

    Each draft names the specific thing that prompted it, and reads like a person wrote it.

    1. Two or three messages you actually sent that got replies. This does more for quality than any amount of prompt instruction.

      Files config/examples.md

    2. Check for a distinctive token from the evidence in the draft. Regenerate once on failure, then hand it to a human.

      Files src/draft.js

    3. If they blur together, the prompt is producing a template. Fix that before anything is sent to a real person.

    done when · tick each as it passes
    watch out
    • A model given only a name and a company writes the same message every time, and recipients have seen it a thousand times. The evidence text is what makes the note worth reading, so make citing it a validated requirement rather than an instruction you hope was followed.
  5. A review queue with limits that bind

    Nothing is sent automatically, and the daily limit is enforced by the application rather than by your memory.

    1. Approve, edit or skip. Editing is the common case and should be as fast as approving.

      Files src/queue.js

    2. A daily count and a rolling seven day count, both checked before an approval is allowed. Not a warning, a refusal.

      Files src/limits.js

    3. Grep the codebase for any outbound send. The absence is a feature and worth asserting in a test.

      Files test/no-send.test.js

    done when · tick each as it passes
    watch out
    • The temptation at this point is to add a send button that posts through an automation interface, because everything else is built. That is the step that puts the account at risk, and the account is the asset. Keep the human in the loop and keep the caps conservative; limits vary by account age and acceptance rate, so the published ceiling is not your ceiling.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more sales outreach.