Systems Library / Operations & Admin / How to Create Automated Project Status Notifications
Operations & Admin communication

How to Create Automated Project Status Notifications

Notify stakeholders automatically when project milestones change.

Jay Banlasan

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

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

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

Related Systems