Build Dux-Soup

KINDAreplaces $14.99/mosaves $179.88/yrback to the verdict

0%0 of 17 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.

An outreach assistant built around the constraint that you do the sending. It keeps the queue, drafts the message, records the outcome, and enforces the limits that stop follow-up becoming harassment, without ever touching a platform on your behalf.

estimated effort multi-daythe files for this build are in the project pack

RuntimeTypeScript with a local application and SQLiteImportManual entry and file import onlyDraftingLocal model drafting against your own notesLimitsPer-contact and per-week caps enforced in the queueTrackingOutcomes recorded by you, since you sent it

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 local application.

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

    Verify node --version prints v22 or higher

  2. installfree, needs memory

    Why Drafts are built from your private notes about people, which is not material to send to a service by default.

    Get it Any local runner with a model that follows instructions and writes plainly. open ↗

    Verify A draft generates locally from a set of notes

  3. decidefree to read

    Why The build deliberately does no automation, but you should know what the rules are, since the temptation to add it will arrive.

    Get it Read the user agreement of every platform you will send through, particularly the sections on automated access and data extraction.

    Verify A written note states what each platform prohibits

  4. have readyfree

    Why Drafting quality and the refusal behaviour are only testable against real contacts where you either do or do not have specific context.

    Get it People you have actually met or have a genuine reason to approach, with your real notes on each.

    Verify Fifty contacts exist, at least fifteen with no specific context beyond a job title

  5. decidefree

    Why They are constants in the code, and deciding them after using the tool means deciding them when you want more.

    Get it Decide the maximum follow-ups per person, the minimum gap, and the weekly total, from the position of someone receiving them.

    Verify Three numbers are written down with the reasoning

Data model

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

- contacts(id, name, org, role, source, added_at, notes) · source is how you actually met them
- sequences(id, name, steps_json, max_touches, min_gap_days)
- queue(id, contact_id, sequence_id, step, due_on, state, drafted_body, sent_at, skipped_reason)
- outcomes(id, queue_id, kind, at, note) · kind is replied, declined, no_response or asked_to_stop
- suppressions(contact_id, reason, at) · asked to stop is permanent and enforced before anything is queued
- weekly_counts(week_start, sent) · your own volume, visible to you
- There is deliberately no credentials table and no session store, because the build never acts on a platform.

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~/.local/share/outreach/outreach.dbContacts, notes and outcomes. Personal data about identified people.
MAX_TOUCHES_PER_CONTACTrequired3A constant in the queue. Not raisable at runtime.
MIN_GAP_DAYSrequired7Between touches to one person. Three in a week is pestering.
WEEKLY_QUEUE_CAPrequired20Total messages surfaced per week. Volume is what this build declines to provide.
PLATFORM_AUTOMATIONrequirednonePresent as a constant so the answer is visible. There is no other value.
DRAFT_MODELrequiredlocalDrafts are built from private notes about people.

The build, in order

  1. A list of people you actually know of

    Contacts arrive by your hand, with the reason you added them.

    1. Answered, not defaulted. It is the question that keeps the list honest.

      Files src/contacts.ts

    2. There is nothing to store because the build never signs in anywhere.

      Files src/schema.sql

    3. Assert it in the dependency check, so it cannot creep back in.

      Files tests/no-automation.test.ts

    done when · tick each as it passes
    watch out
    • Adding an import path that accepts a scraped export makes the tool a consumer of automated collection even though it does none itself, and the obligations attach to holding the data rather than to gathering it. Require a source per contact and make the field one a person has to answer rather than a dropdown with a default.
  2. Sequences with a ceiling

    A follow-up sequence stops before it becomes pestering.

    1. Configurable becomes nine. The ceiling should not depend on your mood.

      Files src/limits.ts

    2. Three messages in a week is not a sequence, it is pestering.

      Files src/queue.ts

    3. Volume is the thing this build declines to provide.

      Files src/limits.tssrc/limits.test.ts

    done when · tick each as it passes
    watch out
    • A configurable touch limit becomes six, then nine, because each individual increase seems reasonable and the person on the other end is not in the room. Make the ceiling a constant, and make the weekly cap one too, so the tool's restraint does not depend on your restraint.
  3. Drafts that sound like you wrote them

    A drafted message is specific enough to be worth sending.

    1. A generic message is worse than none and this is where volume comes from.

      Files src/draft.ts

    2. So you can see whether the draft has anything real in it.

      Files src/draft.ts

    3. You will edit it. The record should be the truth.

      Files src/queue.ts

    done when · tick each as it passes
    watch out
    • A drafter that produces a plausible message from a name and a job title generates exactly the outreach everyone deletes, and it does so at whatever volume you allow. Require specific context, and refuse rather than producing a generic message, since the refusal is more useful than the draft.
  4. You send it, and you say what happened

    The record reflects reality because you entered it.

    1. Not at send. A suppressed contact should never appear.

      Files src/queue.tstests/suppression.test.ts

    2. Including across re-import. It is not a preference.

      Files src/suppressions.ts

    3. The record is honest because you made it.

      Files src/queue.ts

    done when · tick each as it passes
    watch out
    • Enforcing a suppression at send time means the person still appears in your queue every week, and one distracted click puts you back in their inbox after they asked you not to be. Check suppressions when queueing, so a suppressed contact never surfaces at all.
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.