All posts
Workflow

Reddit + Slack alerts: the buyer-intent workflow that fires

By Bazzly Team4 min read

A production-tested PRAW 8 + Slack webhook workflow that pipes only high-signal, buyer-intent Reddit threads into your channel without drowning it in noise.

Reddit + Slack alerts: the buyer-intent workflow that fires

Most Reddit-to-Slack alerts fail the same way: you wire up a keyword match, the channel fills with garbage inside a day, and by week two nobody looks at it. The problem is never the pipe. It's the filter.

Here's the workflow we actually run in production. It uses PRAW 8 on the Reddit side and a Slack incoming webhook on the other. Between them sits the part everyone skips: a scoring function that decides whether a post is buying-intent or noise.

What the pipe looks like

Three moving parts. That's it.

Rendering diagram…

The streaming part is trivial. The scoring part is where 90% of the value lives. If you skip straight from PRAW to requests.post(webhook_url, ...) on any keyword match, your channel will die by Friday.

Step 1: PRAW 8 streaming setup

PRAW 8 dropped in late 2025 with a cleaner async story and better rate-limit handling. For a Slack alerter you don't need async — a single blocking process watching a multireddit is fine and cheaper to run.

Create a script-type app at reddit.com/prefs/apps, grab the client ID and secret, then:

import praw

reddit = praw.Reddit(
    client_id=CLIENT_ID,
    client_secret=CLIENT_SECRET,
    user_agent="buyer-intent-alerts/1.0 by u/yourhandle",
    username=USERNAME,
    password=PASSWORD,
)

subs = reddit.subreddit("SaaS+startups+Entrepreneur+microsaas+indiehackers")

for submission in subs.stream.submissions(skip_existing=True):
    if score(submission) >= THRESHOLD:
        post_to_slack(submission)

skip_existing=True matters. Without it, the first run backfills the last 100 posts per sub and you'll wake up to Slack rate-limiting you.

The user agent string is not optional. Reddit's API rules require a unique, descriptive UA. Generic ones get throttled or blocked outright, which is one of the things the May 2026 scraper crackdown tightened further.

Step 2: the scoring function (this is the whole game)

A keyword like "looking for" fires on "I'm looking for a good pizza place". A keyword like "any recommendations" fires on someone asking about Netflix shows. Single-keyword matching is useless. You need at least two signals in the same post.

Here's the pattern that actually works: score for problem signal, buyer signal, and niche signal, then require the sum to clear a threshold.

PROBLEM_PATTERNS = [
    r"\bcan(?:'t| not) find\b",
    r"\bfrustrat(ed|ing)\b",
    r"\btired of\b",
    r"\bwhy is (there|it) no\b",
    r"\bany (tool|app|service) that\b",
]

BUYER_PATTERNS = [
    r"\brecommend(?:ations?|ing)?\b",
    r"\bwilling to pay\b",
    r"\bbudget (of|is|around)\b",
    r"\balternatives? to\b",
    r"\bcompar(?:ing|e)\b",
]

NICHE_KEYWORDS = ["onboarding", "churn", "analytics", "crm", "stripe"]

def score(submission):
    text = (submission.title + " " + submission.selftext).lower()
    s = 0
    if any(re.search(p, text) for p in PROBLEM_PATTERNS): s += 2
    if any(re.search(p, text) for p in BUYER_PATTERNS): s += 3
    if any(k in text for k in NICHE_KEYWORDS): s += 2
    if submission.author and submission.author.link_karma < 50: s -= 2
    if len(submission.selftext) < 60: s -= 1
    return s

Threshold of 5 or 6 is a good starting point. The karma penalty catches throwaway accounts that mostly post low-quality asks. The length penalty kills one-liners that never contain enough context to reply to.

Log every dropped post for a week. Read the log on day 8 and adjust. Nothing else improves signal quality as fast.

Step 3: Slack webhook side

Create an app at api.slack.com/apps, enable Incoming Webhooks, add one to your target channel. You get a URL. Treat it like a password.

import requests, json

def post_to_slack(sub):
    payload = {
        "blocks": [
            {"type": "section", "text": {"type": "mrkdwn",
                "text": f"*<https://reddit.com{sub.permalink}|{sub.title}>*\n"
                        f"r/{sub.subreddit.display_name}  ·  u/{sub.author}  ·  karma {sub.author.link_karma if sub.author else '?'}"}},
            {"type": "section", "text": {"type": "mrkdwn",
                "text": sub.selftext[:400] + ("..." if len(sub.selftext) > 400 else "")}},
        ]
    }
    requests.post(WEBHOOK_URL, data=json.dumps(payload),
                  headers={"Content-Type": "application/json"}, timeout=10)

Block Kit beats plain text for one reason: the link is clickable and the metadata line (subreddit, author, karma) helps you triage the alert in half a second. That's the whole design goal. If you have to click into every message to decide whether it's worth reading, the channel loses.

Step 4: the sequence when a real lead arrives

Rendering diagram…

The 20-minute reply window is the whole reason the pipe exists. On active subs, threads get 80% of their eventual comments in the first two hours. If your alert lands 6 hours late, you're arriving to a dead thread. This is exactly why the "first paying user" reply tactic hinges on being early, not clever.

Operational details that matter

Rate limits. PRAW handles Reddit's limits for you. Slack's webhook limit is roughly 1 message per second per hook. If your threshold is tuned right you'll never hit it, but throw a time.sleep(0.5) between posts if you ever backfill.

Deduplication. skip_existing=True covers restart cases, but if you run multiple workers, keep a Redis set of submission IDs seen in the last hour. Same submission alerting twice is a fast way to get muted.

Deleted posts. By the time you open Slack, the post may be gone. Include the permalink and the first 400 chars of selftext in the payload so you can at least see what it was.

Multireddit hygiene. Don't watch 40 subreddits from one process. Group them by topic and run 2-3 processes writing to 2-3 channels. #reddit-leads-saas, #reddit-leads-devtools, etc. Mixing niches in one channel guarantees nobody reads it.

Author signals. The karma check above is the minimum. If you want to go further, check submission.author.created_utc — accounts under 30 days old asking product questions are 60% throwaways in our logs. Not a hard reject, but a score penalty.

When to build vs. buy

This whole script is maybe 120 lines. Running it costs a $5 VPS. If you have engineers and want full control of the scoring logic, build it.

If you'd rather not maintain a PRAW worker, tune regex patterns for six weeks, and rewrite the scorer every time Reddit changes its rate-limit behavior, Bazzly does the scanning and scoring for you and drops the same kind of filtered alerts into Slack. Same shape of workflow, minus the tuning tax.

Either way, the rule stands: the pipe is the easy part. The filter is the product.

What to build next

Once the Slack channel is quietly useful, the natural next steps are:

  • Reply-time SLA tracking: log time between alert and your first Reddit comment, aim under 30 minutes.
  • Comment stream alongside submissions: subs.stream.comments() catches product mentions inside threads that were never about you.
  • Weekly digest: aggregate the week's alerts by subreddit and pattern, so you can see which signal keywords are drifting.

Build those in that order. Skipping to comment streams before the submission alerter is tuned just doubles the noise problem.

Related reading