Daily Python Projects

Daily Python Projects

Desktop Notification System: Day 3 - Smart Notification Manager with Persistence

Today we're creating a complete notification management system with saved reminders, templates, and a persistent scheduler that remembers everything!

Ardit Sulce's avatar
Ardit Sulce
May 07, 2026
∙ Paid

Projects in this week’s series:

This week, we build a Desktop Notification System that displays toast notifications, schedules reminders, and manages alerts — perfect for building productivity tools!

  • Day 1: Simple Desktop Notifications

  • Day 2: Scheduled Notifications & Timers

  • Day 3: Smart Notification Manager with Persistence (Today)

View All Projects This Week

Today’s Project

Welcome to the finale! We’ve built notifications and timers. Today we’re creating a complete notification management system with saved reminders, templates, and a persistent scheduler that remembers everything!

You’ll build a manager that saves your notifications, loads them on startup, and runs multiple scheduled reminders simultaneously!

Project Task

Create a complete notification management system with:

  • Save notifications to JSON file

  • Load saved notifications on startup

  • Manage multiple scheduled notifications

  • Create reusable notification templates

  • List all active/scheduled notifications

  • Cancel specific notifications

  • Edit and update notifications

  • Persistent storage between sessions

  • Clean command-line interface

This project gives you hands-on practice with JSON persistence, data management, state tracking, CRUD operations, and building production-ready tools — essential skills for real-world applications!

Expected Output

Running the notification manager:

python solution.py

Console Output:

First you need to set up a scheduled notification in the terminal. Notice that we are also creating a template out of it.

Then, the notification will trigger as a native desktop notification:

Saved JSON file: notifications_data.json

{
  "notifications": [
    {
      "id": "notif_001",
      "title": "Team Meeting",
      "message": "Weekly sync starts now",
      "type": "scheduled",
      "time": "14:00",
      "status": "active",
      "created_at": "2026-04-26T10:15:00",
      "template_name": "Weekly Team Meeting"
    },
    {
      "id": "notif_002",
      "title": "Hydration Reminder",
      "message": "Time to drink water!",
      "type": "recurring",
      "interval_minutes": 60,
      "status": "active",
      "created_at": "2026-04-26T10:20:00",
      "template_name": "Hydration Reminder"
    }
  ],
  "templates": [
    {
      "name": "Daily Standup",
      "type": "scheduled",
      "time": "09:30",
      "title": "Daily Standup",
      "message": "Morning team sync",
      "use_count": 15
    },
    {
      "name": "Hydration Reminder",
      "type": "recurring",
      "interval_minutes": 60,
      "title": "Hydration Reminder",
      "message": "Time to drink water!",
      "use_count": 5
    }
  ]
}

Setup Instructions

No new installations needed!

Same setup as Days 1 & 2:

macOS:

python solution.py

Windows:

pip install win10toast  # If not already installed
python solution.py

Linux:

python solution.py

Data file:

  • Creates notifications_data.json automatically

  • Loads on startup

  • Saves on exit or after changes

Understanding Data Persistence

Why persist data?

Without persistence:

  • ❌ All notifications lost when program closes

  • ❌ Must recreate reminders every time

  • ❌ Can’t track notification history

With persistence:

  • ✅ Notifications survive program restarts

  • ✅ Templates saved for reuse

  • ✅ History of completed notifications

  • ✅ Resume where you left off

Our approach:

# On startup
def load():
    with open('notifications_data.json', 'r') as f:
        data = json.load(f)
    return data

# On exit or after changes
def save():
    with open('notifications_data.json', 'w') as f:
        json.dump(data, f, indent=2)

Understanding the Data Structure

notifications_data.json structure:

{
  "notifications": [
    {
      "id": "notif_001",           // Unique identifier
      "title": "Meeting",          // Notification title
      "message": "Team sync",      // Notification message
      "type": "scheduled",         // Type: scheduled/recurring/instant
      "time": "14:00",            // For scheduled notifications
      "interval_minutes": 60,      // For recurring notifications
      "status": "active",          // Status: active/completed/cancelled
      "created_at": "2026-04-26T10:15:00",
      "template_name": "Weekly Meeting"  // Optional
    }
  ],
  "templates": [
    {
      "name": "Daily Standup",     // Template name
      "type": "scheduled",         // Template type
      "time": "09:30",            // Template time
      "title": "Daily Standup",    // Default title
      "message": "Morning sync",   // Default message
      "use_count": 15             // Usage tracking
    }
  ]
}

Key fields:

  • id - Unique identifier (e.g., notif_001)

  • type - scheduled, recurring, instant, countdown

  • status - active, completed, cancelled

  • created_at - When notification was created

  • template_name - Links to template (optional)

Understanding Notification States

Notification lifecycle:

Created → Active → Completed
           ↓
       Cancelled

State definitions:

Active:

  • Scheduled in the future

  • Currently recurring

  • Waiting to be sent

Completed:

  • Already sent

  • Countdown finished

  • One-time notification done

Cancelled:

  • User manually cancelled

  • Removed before sending

State transitions:

# Create notification
notification['status'] = 'active'

# Send notification
notification['status'] = 'completed'
notification['completed_at'] = datetime.now().isoformat()

# Cancel notification
notification['status'] = 'cancelled'
notification['cancelled_at'] = datetime.now().isoformat()

Understanding Templates

What are templates?

Templates are saved notification configurations you can reuse:

# Template
{
  "name": "Daily Standup",
  "type": "scheduled",
  "time": "09:30",
  "title": "Daily Standup",
  "message": "Morning team sync"
}

# Create from template
notification = create_from_template("Daily Standup")
# Automatically fills in all fields

Template benefits:

✅ Consistency - Same notification every time
✅ Speed - One click instead of typing
✅ Tracking - See which templates are most used
✅ Reusability - Use same reminder daily/weekly

Common templates:

  • Daily standup (9:30 AM)

  • Lunch break (12:00 PM)

  • End of workday (5:00 PM)

  • Hydration reminder (every 1 hour)

  • Pomodoro session (25 minute timer)

Understanding Notification IDs

Why unique IDs?

Need a way to identify specific notifications:

# Without IDs - ambiguous!
cancel_notification("Team Meeting")  # Which one?

# With IDs - precise!
cancel_notification("notif_001")  # Exact notification

ID generation:

def generate_id(self):
    # Get highest existing ID number
    existing_ids = [n['id'] for n in self.notifications]
    
    if not existing_ids:
        return "notif_001"
    
    # Extract numbers and increment
    numbers = [int(id.split('_')[1]) for id in existing_ids]
    next_num = max(numbers) + 1
    
    return f"notif_{next_num:03d}"

Understanding Multi-Notification Management

Managing multiple scheduled notifications:

# Track all active threads
active_threads = []

# Start notification 1
thread1 = threading.Thread(target=send_later, args=("notif_001",))
thread1.start()
active_threads.append(thread1)

# Start notification 2
thread2 = threading.Thread(target=send_later, args=("notif_002",))
thread2.start()
active_threads.append(thread2)

# Both running simultaneously!

Cancelling specific notifications:

# Each thread has reference to notification ID
# Store stop flags in dictionary

stop_flags = {}

def send_later(notif_id):
    while not stop_flags.get(notif_id, False):
        # Wait and check if cancelled
        time.sleep(1)
    
    # Send notification
    send_notification(...)

# To cancel
stop_flags[notif_id] = True

Understanding Auto-Reload on Startup

Resume notifications on startup:

def load_and_schedule(self):
    # Load from JSON
    data = self.load_data()
    
    # Find active scheduled notifications
    for notif in data['notifications']:
        if notif['status'] == 'active':
            if notif['type'] == 'scheduled':
                # Reschedule if time hasn't passed
                self._reschedule_notification(notif)
            elif notif['type'] == 'recurring':
                # Restart recurring reminder
                self._restart_recurring(notif)

What gets rescheduled:

✅ Scheduled notifications - If time is in future
✅ Recurring reminders - Always restart
❌ Completed notifications - Already sent
❌ Past scheduled times - Mark as missed

What You’ve Accomplished This Week

  • Day 1: Desktop notifications with cross-platform support

  • Day 2: Scheduling, timers, and Pomodoro

  • Day 3: Persistence, templates, and full management

You now have:

✅ Cross-platform notifications - Works on Mac, Windows, Linux
✅ Flexible scheduling - Instant, scheduled, recurring, countdown
✅ Persistent storage - Survives program restarts
✅ Template system - Reusable notification configs
✅ Full management - Create, list, cancel, edit
✅ Production-ready - Clean code, error handling, JSON storage

View Code Evolution

Compare today’s full management system with earlier versions and see how we evolved from simple notifications → scheduling → complete persistence.

Keep reading with a 7-day free trial

Subscribe to Daily Python Projects to keep reading this post and get 7 days of free access to the full post archives.

Already a paid subscriber? Sign in
© 2026 Ardit Sulce · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture