Operations & Admin
communication
How to Create Automated Project Status Notifications
Notify stakeholders automatically when project milestones change.
Jay Banlasan
The AI Systems Guy
Stakeholders should not have to ask for project updates. I built a system to automate project status notifications that fires alerts when milestones complete, deadlines change, or blockers appear. The right people get the right update at the right time.
Push beats pull. Proactive notifications keep everyone aligned.
What You Need Before Starting
- Python 3.8+
- A project tracking database or PM tool API
- Stakeholder notification preferences
- Slack or email for delivery
Step 1: Define Notification Triggers
NOTIFICATION_TRIGGERS = {
"milestone_complete": {
"notify": ["project_owner", "stakeholders"],
"template": "Milestone '{milestone}' completed for {project}."
},
"deadline_changed": {
"notify": ["project_owner", "stakeholders", "team"],
"template": "Deadline changed for {project}: {old_date} -> {new_date}. Reason: {reason}"
},
"blocker_added": {
"notify": ["project_owner", "team_lead"],
"template": "New blocker on {project}: {description}"
},
"status_changed": {
"notify": ["stakeholders"],
"template": "{project} status changed: {old_status} -> {new_status}"
},
"budget_threshold": {
"notify": ["project_owner", "finance"],
"template": "{project} has used {percent}% of budget (${spent} of ${total})"
}
}
Step 2: Monitor for Changes
import sqlite3
from datetime import datetime
def init_notification_db(db_path="notifications.db"):
conn = sqlite3.connect(db_path)
conn.execute("""
CREATE TABLE IF NOT EXISTS project_state (
project_id TEXT PRIMARY KEY,
project_name TEXT,
status TEXT,
current_milestone TEXT,
deadline TEXT,
budget_spent REAL,
budget_total REAL,
last_checked TEXT
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS notification_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id TEXT,
trigger_type TEXT,
message TEXT,
recipients TEXT,
sent_at TEXT
)
""")
conn.commit()
return conn
def detect_changes(conn, project_id, new_state):
old = conn.execute(
"SELECT status, deadline, budget_spent FROM project_state WHERE project_id=?",
(project_id,)
).fetchone()
changes = []
if old:
if old[0] != new_state.get("status"):
changes.append({"trigger": "status_changed", "old_status": old[0], "new_status": new_state["status"]})
if old[1] != new_state.get("deadline"):
changes.append({"trigger": "deadline_changed", "old_date": old[1], "new_date": new_state["deadline"]})
if new_state.get("budget_total") and new_state["budget_spent"] / new_state["budget_total"] > 0.8:
if not old[2] or old[2] / new_state["budget_total"] <= 0.8:
changes.append({"trigger": "budget_threshold",
"percent": round(new_state["budget_spent"]/new_state["budget_total"]*100)})
return changes
Step 3: Send Notifications
import requests
def send_notification(trigger_type, project_name, data, recipients, slack_token):
config = NOTIFICATION_TRIGGERS[trigger_type]
message = config["template"].format(project=project_name, **data)
for recipient in recipients:
requests.post(
"https://slack.com/api/chat.postMessage",
headers={"Authorization": f"Bearer {slack_token}"},
json={"channel": recipient, "text": message}
)
return message
Step 4: Respect Notification Preferences
STAKEHOLDER_PREFS = {
"ceo": {"channels": ["slack"], "frequency": "milestones_only", "quiet_hours": "22:00-07:00"},
"project_manager": {"channels": ["slack", "email"], "frequency": "all", "quiet_hours": None},
"client": {"channels": ["email"], "frequency": "weekly_digest", "quiet_hours": None}
}
def should_notify(stakeholder, trigger_type):
prefs = STAKEHOLDER_PREFS.get(stakeholder, {})
freq = prefs.get("frequency", "all")
if freq == "all":
return True
elif freq == "milestones_only" and trigger_type == "milestone_complete":
return True
elif freq == "blockers_and_milestones" and trigger_type in ["milestone_complete", "blocker_added"]:
return True
return False
Step 5: Run the Monitor
def run_project_monitor(conn, projects, slack_token):
for project in projects:
changes = detect_changes(conn, project["id"], project)
for change in changes:
trigger = change.pop("trigger")
config = NOTIFICATION_TRIGGERS[trigger]
recipients = [r for r in config["notify"] if should_notify(r, trigger)]
if recipients:
msg = send_notification(trigger, project["name"], change, recipients, slack_token)
conn.execute(
"INSERT INTO notification_log (project_id, trigger_type, message, recipients, sent_at) VALUES (?,?,?,?,?)",
(project["id"], trigger, msg, ",".join(recipients), datetime.now().isoformat())
)
conn.commit()
# Schedule: every 15 minutes
# */15 * * * * python3 /path/to/project_monitor.py
What to Build Next
Add digest mode that batches non-urgent notifications into a daily or weekly summary. Not every change needs an instant alert. Grouping reduces noise while keeping stakeholders informed.
Related Reading
- AI-Powered Reporting That Actually Gets Read - making notifications worth reading
- The Feedback Loop That Powers Everything - notification feedback loops
- Building Your First Automation: A Complete Guide - automation fundamentals
Want this system built for your business?
Get a free assessment. We will map every system your business needs and show you the ROI.
Get Your Free Assessment