Tutorial

How to Build an App Sentiment Alert Pipeline

Detect app sentiment shifts with automated search monitoring. Track review tone, feature complaints, and competitor sentiment across app stores.

Build an app sentiment alert pipeline that monitors how users talk about your app across the web, detects sentiment shifts by analyzing search result snippets, and triggers alerts when negative sentiment spikes or positive sentiment drops. App store reviews only capture a fraction of user sentiment. Users also discuss apps on Reddit, review blogs, forums, and social media. By searching for your app name daily and analyzing the tone of search results, you catch sentiment shifts from all these sources through a single API.

Prerequisites

  • Python 3.8+ installed
  • requests library installed
  • A Scavio API key from scavio.dev
  • App names to monitor

Walkthrough

Step 1: Define monitoring targets

Configure the apps and search queries used for sentiment monitoring.

Python
import os, requests, json, datetime

API_KEY = os.environ['SCAVIO_API_KEY']

MONITORED_APPS = [
    {'name': 'MyApp', 'queries': ['MyApp review', 'MyApp problems', 'MyApp vs competitor']},
    {'name': 'CompetitorApp', 'queries': ['CompetitorApp review', 'CompetitorApp issues']},
]

POSITIVE_WORDS = ['love', 'great', 'excellent', 'best', 'amazing', 'fast', 'reliable', 'smooth', 'perfect', 'recommend']
NEGATIVE_WORDS = ['slow', 'buggy', 'crash', 'terrible', 'worst', 'broken', 'expensive', 'frustrating', 'disappointed', 'awful']

HISTORY_FILE = 'sentiment_history.json'

print(f'Monitoring {len(MONITORED_APPS)} apps')

Step 2: Search and collect mentions

Search for each app across Google and Reddit to collect current mentions.

Python
import time

def collect_mentions(app: dict) -> list:
    mentions = []
    for query in app['queries']:
        for platform in ['google', 'reddit']:
            resp = requests.post('https://api.scavio.dev/api/v1/search',
                headers={'x-api-key': API_KEY},
                json={'platform': platform, 'query': query}, timeout=15)
            results = resp.json().get('organic_results', [])
            for r in results[:5]:
                mentions.append({
                    'platform': platform,
                    'title': r.get('title', ''),
                    'snippet': r.get('snippet', ''),
                    'url': r.get('link', ''),
                    'query': query,
                })
            time.sleep(0.2)
    return mentions

mentions = collect_mentions(MONITORED_APPS[0])
print(f'Collected {len(mentions)} mentions for {MONITORED_APPS[0]["name"]}')

Step 3: Analyze sentiment

Score the sentiment of each mention based on positive and negative word presence.

Python
def analyze_mention_sentiment(mention: dict) -> dict:
    text = (mention.get('title', '') + ' ' + mention.get('snippet', '')).lower()
    pos = sum(1 for w in POSITIVE_WORDS if w in text)
    neg = sum(1 for w in NEGATIVE_WORDS if w in text)
    if pos > neg:
        sentiment = 'positive'
    elif neg > pos:
        sentiment = 'negative'
    else:
        sentiment = 'neutral'
    return {
        **mention,
        'sentiment': sentiment,
        'pos_score': pos,
        'neg_score': neg,
    }

def batch_sentiment(mentions: list) -> dict:
    analyzed = [analyze_mention_sentiment(m) for m in mentions]
    pos = sum(1 for a in analyzed if a['sentiment'] == 'positive')
    neg = sum(1 for a in analyzed if a['sentiment'] == 'negative')
    neutral = sum(1 for a in analyzed if a['sentiment'] == 'neutral')
    total = len(analyzed)
    return {
        'total': total,
        'positive': pos,
        'negative': neg,
        'neutral': neutral,
        'sentiment_score': round((pos - neg) / total, 2) if total > 0 else 0,
        'mentions': analyzed,
    }

result = batch_sentiment(mentions)
print(f"Sentiment: +{result['positive']} -{result['negative']} ={result['neutral']}")
print(f"Score: {result['sentiment_score']}")

Step 4: Detect sentiment shifts

Compare current sentiment against historical baseline to detect significant changes.

Python
def store_sentiment(app_name: str, sentiment: dict):
    history = []
    try:
        with open(HISTORY_FILE) as f:
            history = json.load(f)
    except FileNotFoundError:
        pass
    history.append({
        'app': app_name,
        'date': datetime.date.today().isoformat(),
        'score': sentiment['sentiment_score'],
        'positive': sentiment['positive'],
        'negative': sentiment['negative'],
        'total': sentiment['total'],
    })
    with open(HISTORY_FILE, 'w') as f:
        json.dump(history, f, indent=2)

def detect_shift(app_name: str, current_score: float) -> dict:
    try:
        with open(HISTORY_FILE) as f:
            history = json.load(f)
    except FileNotFoundError:
        return {'shift': 'no_baseline'}
    entries = [h for h in history if h['app'] == app_name]
    if len(entries) < 3:
        return {'shift': 'insufficient_data'}
    recent = entries[-5:]
    avg = sum(e['score'] for e in recent) / len(recent)
    change = current_score - avg
    return {
        'shift': 'negative' if change < -0.15 else 'positive' if change > 0.15 else 'stable',
        'change': round(change, 2),
        'baseline_avg': round(avg, 2),
        'current': current_score,
    }

store_sentiment('MyApp', result)
shift = detect_shift('MyApp', result['sentiment_score'])
print(f"Shift: {shift['shift']} (change: {shift.get('change', 'N/A')})")

Step 5: Generate alerts

Trigger alerts when sentiment drops below threshold or shifts negatively.

Python
def check_alerts(apps: list) -> list:
    alerts = []
    for app in apps:
        mentions = collect_mentions(app)
        sentiment = batch_sentiment(mentions)
        store_sentiment(app['name'], sentiment)
        shift = detect_shift(app['name'], sentiment['sentiment_score'])
        if shift['shift'] == 'negative':
            alert = {
                'app': app['name'],
                'type': 'sentiment_drop',
                'score': sentiment['sentiment_score'],
                'change': shift.get('change', 0),
                'negative_mentions': [m['title'][:60] for m in sentiment['mentions'] if m['sentiment'] == 'negative'][:3],
            }
            alerts.append(alert)
            print(f"ALERT: {app['name']} sentiment dropped ({shift['change']})")
            for m in alert['negative_mentions']:
                print(f"  - {m}")
        else:
            print(f"{app['name']}: {shift['shift']} (score: {sentiment['sentiment_score']})")
    return alerts

alerts = check_alerts(MONITORED_APPS)

Python Example

Python
import requests, os
H = {'x-api-key': os.environ['SCAVIO_API_KEY']}
POS = ['love', 'great', 'best', 'fast']
NEG = ['slow', 'buggy', 'crash', 'terrible']

def sentiment(app):
    data = requests.post('https://api.scavio.dev/api/v1/search', headers=H,
        json={'platform': 'google', 'query': f'{app} review'}).json()
    results = data.get('organic_results', [])[:5]
    pos = neg = 0
    for r in results:
        text = (r.get('snippet', '')).lower()
        pos += sum(1 for w in POS if w in text)
        neg += sum(1 for w in NEG if w in text)
    return {'app': app, 'positive': pos, 'negative': neg}

print(sentiment('Slack'))

JavaScript Example

JavaScript
const H = {'x-api-key': process.env.SCAVIO_API_KEY, 'Content-Type': 'application/json'};
const POS = ['love', 'great', 'best', 'fast'];
const NEG = ['slow', 'buggy', 'crash', 'terrible'];
async function sentiment(app) {
  const r = await fetch('https://api.scavio.dev/api/v1/search', {
    method: 'POST', headers: H,
    body: JSON.stringify({platform: 'google', query: `${app} review`})
  });
  const results = (await r.json()).organic_results || [];
  let pos = 0, neg = 0;
  results.slice(0, 5).forEach(r => {
    const t = (r.snippet || '').toLowerCase();
    POS.forEach(w => { if (t.includes(w)) pos++; });
    NEG.forEach(w => { if (t.includes(w)) neg++; });
  });
  return {app, positive: pos, negative: neg};
}
sentiment('Slack').then(console.log);

Expected Output

JSON
An automated sentiment monitoring pipeline that tracks app mentions across web and Reddit, detects sentiment shifts, and triggers alerts when negative sentiment spikes.

Related Tutorials

Frequently Asked Questions

Most developers complete this tutorial in 15 to 30 minutes. You will need a Scavio API key (free tier works) and a working Python or JavaScript environment.

Python 3.8+ installed. requests library installed. A Scavio API key from scavio.dev. App names to monitor. A Scavio API key gives you 250 free credits per month.

Yes. The free tier includes 250 credits per month, which is more than enough to complete this tutorial and prototype a working solution.

Scavio has a native LangChain package (langchain-scavio), an MCP server, and a plain REST API that works with any HTTP client. This tutorial uses the raw REST API, but you can adapt to your framework of choice.

Start Building

Detect app sentiment shifts with automated search monitoring. Track review tone, feature complaints, and competitor sentiment across app stores.