Nothing kills deal momentum faster than a stage change that nobody notices. When a prospect moves to Negotiation or a deal slips back to Discovery, your sales leaders need to know immediately, not in next week’s pipeline review. This bot watches Salesforce and posts deal alerts directly to Slack in real time.
Architecture Overview¶
The bot runs as a simple Python script on a schedule. Every few minutes it queries Salesforce for recently modified opportunities, compares their current stage to the last known stage, and posts a formatted message to Slack when something changes.
| Component | Tool | Purpose |
|---|---|---|
| CRM connection | simple_salesforce | Query opportunity data |
| Alert delivery | slack_sdk | Post messages to channels |
| State tracking | SQLite | Remember last-known stages |
| Scheduling | cron / Task Scheduler | Run the script periodically |
Setting Up Dependencies¶
pip install simple-salesforce slack-sdk
You will need a Slack Bot Token from your Slack workspace. Create a new app at api.slack.com, give it the chat:write scope, and install it to your workspace.
Tracking Stage Changes¶
The key challenge is knowing when a stage actually changed. The bot stores the last-known stage for each opportunity in a lightweight SQLite database:
import sqlite3
def get_db():
conn = sqlite3.connect("deal_stages.db")
conn.execute("""
CREATE TABLE IF NOT EXISTS stages (
opp_id TEXT PRIMARY KEY,
stage TEXT,
last_checked TEXT
)
""")
return conn
def has_stage_changed(conn, opp_id, current_stage):
row = conn.execute(
"SELECT stage FROM stages WHERE opp_id = ?", (opp_id,)
).fetchone()
if row is None or row[0] != current_stage:
conn.execute(
"REPLACE INTO stages VALUES (?, ?, datetime('now'))",
(opp_id, current_stage),
)
conn.commit()
return row is not None # Only alert on changes, not new records
return False
Querying Salesforce for Recent Changes¶
Pull opportunities modified in the last 15 minutes to catch any changes since the last run:
from simple_salesforce import Salesforce
import os
sf = Salesforce(
username=os.environ["SF_USERNAME"],
password=os.environ["SF_PASSWORD"],
security_token=os.environ["SF_TOKEN"],
)
query = """
SELECT Id, Name, StageName, Amount, Owner.Name
FROM Opportunity
WHERE LastModifiedDate = LAST_N_MINUTES:15
AND Amount > 0
"""
results = sf.query_all(query)
Posting to Slack¶
When a stage change is detected, the bot sends a rich message to your designated Slack channel:
from slack_sdk import WebClient
slack = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
def post_deal_alert(opp):
amount = f"${opp['Amount']:,.0f}"
msg = (
f":rotating_light: *Deal Stage Change*\n"
f"*{opp['Name']}* moved to *{opp['StageName']}*\n"
f"Amount: {amount} | Owner: {opp['Owner']['Name']}"
)
slack.chat_postMessage(channel="#deal-alerts", text=msg)
Putting It All Together¶
The main loop ties everything together:
def main():
conn = get_db()
results = sf.query_all(query)
for record in results["records"]:
if has_stage_changed(conn, record["Id"], record["StageName"]):
post_deal_alert(record)
conn.close()
if __name__ == "__main__":
main()
Schedule it with cron to run every 10 minutes:
*/10 * * * * cd /path/to/bot && python deal_alerts.py
Key Takeaways¶
- A Slack deal alert bot can be built in under 100 lines of Python using simple_salesforce and slack_sdk
- SQLite provides a lightweight way to track stage state without adding infrastructure
- Scheduling with cron keeps the solution simple and avoids the complexity of webhooks or streaming APIs
- The bot ensures deal movement is visible to the entire team the moment it happens, not days later in a pipeline review