Build Bazzly
KINDAreplaces $99/mosaves $1,188/yrback to the verdict
A Reddit listener that finds threads worth replying to and drafts the reply, whose hard problems are staying inside the documented API and its published limits, keeping model spend below the subscription it replaces, and drafting something that does not invent a feature.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the poller, the scorer and the review page in one process.
Get it python.org, or your package manager. open ↗
Verify
python3 --version prints 3.12 or higher - accountfree
Why The only account this build ever touches, and the one a person will post from by hand.
Get it Create a script-type application in your account's app preferences and note the client id, secret and redirect.
Verify
A token request succeeds and an authenticated listing returns with rate limit headers - API keya few pounds a month at the ceiling suggested here
Why Relevance scoring and drafting both go through it, and it is the only recurring cost in the build.
Get it Any provider whose pricing you can read per token.
Verify
One call returns valid JSON and its cost is recorded - have readyfree
Why It is the only source a draft may make a claim from, so without it the drafting step is disabled rather than creative.
Get it Write what the product does, what it costs, and three things it does not do.
Verify
Every claim in a generated draft maps to a line in it - have readya few pounds a month, or free if you own the box
Why A thread is worth answering in its first hours, and a laptop that sleeps turns the listener into a weekly digest.
Get it A small always-on box, a virtual machine, or a home server.
Verify
A poll scheduled for four in the morning has rows by breakfast - decidefree
Why Both are asserted by tests, and deciding them after seeing a bill or a ban means choosing whatever already happened.
Get it Write the promotion policy for each subreddit you watch, with the date you read its rules, and pick a ceiling in pennies per day.
Verify
Both appear in configuration and are named in the tests
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- subreddits(name, rules_fetched_at, promo_policy, allows_links, reviewed_by, reviewed_at) · what each community permits, recorded by a person - seen(fullname, kind, subreddit, created_utc, first_seen_at) · the dedup table, keyed on Reddit's own identifier - candidates(fullname, permalink, title, body_excerpt, author, subreddit, matched_terms_json, prefilter_score) - scores(fullname, model, relevance, asking_for_recommendation, reason, draft_reply, tokens_in, tokens_out, cost_cents, at) - decisions(fullname, action, acted_at, note) · queued, dismissed, irrelevant, posted_by_hand - api_calls(at, endpoint, status, ratelimit_remaining, ratelimit_reset, retry_after) · the evidence when you get throttled - facts(id, claim, source) · what your product actually does, and the only claims a draft is allowed to make - There is no posts table and no send function, because this build never writes anything to Reddit.
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.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATABASE_PATH | required | ./data/radar.db | Candidates, scores, decisions and the call log. |
REDDIT_CLIENT_ID | required | a1B2c3D4e5F6g7 | From your script application's settings. |
REDDIT_CLIENT_SECRETsecret | required | sk-...redacted | Revocable on its own, which is the point of a script application. |
REDDIT_REFRESH_TOKENsecret | required | sk-...redacted | Revoking this is the test for the pause behaviour in the last phase. |
REDDIT_USER_AGENT | required | reddit-radar/0.1 by your-username | Names the tool and the account. An anonymous agent is a blocked agent. |
LLM_API_KEYsecret | required | sk-...redacted | Scoring and drafting. Every call's cost is recorded against it. |
DAILY_COST_CEILING_PENCE | required | 50 | Checked by the scoring loop. Reaching it stops scoring for the day. |
POLL_INTERVAL_SECONDS | required | 900 | Set against the currently published rate limit, not against this example. |
The build, in order
Reading Reddit the documented way
The listener polls inside the published limits and never counts the same thread twice.
The documented route, with a credential you can revoke without changing your password.
Files
src/reddit/auth.py.env.exampleTitles change, links get crossposted, identifiers do not.
Files
src/reddit/poll.pysrc/db/schema.sqlRecord remaining, reset and retry-after on every call so throttling is visible.
Files
src/reddit/client.pytests/test_backoff.py
done when · tick each as it passeswatch out- Deduping on the title or the link means an edited title, a crosspost and a resubmission all arrive as new, and the queue fills with one thread three times while a genuinely new one scrolls off the end. Key everything on the fullname the API returns, store it before you score it, and treat a fullname you have seen as finished whatever changed about it since.
Two stages, because the model is the bill
Only plausible candidates reach a model call and the day's spend has a ceiling.
The cheapest call is the one you did not make.
Files
src/score/prefilter.pytests/test_prefilter.pyAn unparseable answer leaves the row unscored, never scored zero.
Files
src/score/model.pyA spend limit that only logs is a spend limit that does nothing.
Files
src/score/budget.pytests/test_budget.py
done when · tick each as it passeswatch out- Sending every new post from a busy subreddit to a model looks fine on the first evening and costs more by the end of the month than the subscription you were replacing, and nothing in the interface shows it until the invoice does. Prefilter locally, price each call as it happens, and make the ceiling a condition the scoring loop checks rather than an alert somebody is supposed to read.
Drafts that cannot invent a feature
A draft only makes claims traceable to a fact sheet you wrote.
It is the only source a claim is allowed to come from.
Files
data/facts.yamlsrc/draft/prompt.pyHighlighted in the queue, so the person reads them before copying.
Files
src/draft/verify.pytests/test_claims.pyAsserted in the suite, not left to the prompt's good behaviour.
Files
src/draft/prompt.pytests/test_disclosure.py
done when · tick each as it passeswatch out- A model writing a helpful recommendation will confidently attribute a feature you do not have, and because the draft reads well the person posts it, and the correction then happens in public underneath it. Ground the draft in the fact sheet, mark untraceable claims in a second pass, and treat an unflagged draft as the exception rather than the default.
Community rules as data
A subreddit that forbids promotion never produces a draft.
Stale rules are worse than none, so the date is shown.
Files
src/reddit/rules.pyThe safe default is the one that costs you nothing but a review.
Files
src/policy.pytests/test_policy.pyThe person copying the text should read them at that moment.
Files
src/templates/queue.html
done when · tick each as it passeswatch out- Treating every community the same gets the account banned in the one that bans on a first link, and the ban is not undone by deleting the comment or by explaining that a tool wrote it. Default unreviewed communities to no drafting, keep the rule text beside the draft so the decision is made with it in view, and record who reviewed each policy and when.
A queue you work, and a key you can killproduct builder
The queue is usable daily, the credential can be revoked, and the tool demonstrates with no keys at all.
An empty queue should never be ambiguous.
Files
src/reddit/client.pytests/test_pause.pyDismissed and posted are both answers, and both stop it reappearing.
Files
src/db/decisions.pySo the interface can be judged before anyone registers anything.
Files
src/demo/fixtures.jsonsrc/app.py
done when · tick each as it passeswatch out- A refresh token is revoked or expires and every poll fails quietly for a week, so the first thing you notice is an empty queue, which you read as Reddit being quiet rather than the listener being dead. Pause on the first authentication failure, show the paused state on the queue itself, and alert once.
That is the whole plan for Bazzly. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Posting, voting or messaging of any kind.
- Multiple accounts, rotation or proxies.
- Sentiment and competitor dashboards.
- Browser automation of a logged-in session.
- Aged, high-karma accounts with credible posting history
- Multi-account rotation, proxies, and whatever ban-avoidance heuristics they have learned the hard way
- Their curated map of which subreddits tolerate product mentions and which will nuke you
- A hosted dashboard, team seats, and shared approval queues
- Someone else's problem when the API terms or rate limits change
- A second source such as Hacker News or a forum with a published API, added as another poller behind the same scorer
- A weekly digest email of the top candidates, which the queue already has the data for
- Per-subreddit tuning of the prefilter, once the score rows show where the wasted calls are going
Need the files? The project pack on the verdict page hands your agent the whole brief · more social media.