Skip to content

Simple Reminder App

A reminder app is a tiny program with huge personal-productivity value: type a message and a delay, and the computer pings you when the time arrives. In this tutorial you will build the single-shot version with plyer for desktop notifications, then evolve it into a multi-reminder scheduler that survives restarts, supports absolute times (not just “in N minutes”), runs in the background, and works on Windows, macOS, and Linux.

You will learn:

  • How time.sleep actually works (and when it is the wrong tool).
  • How plyer abstracts notification systems across operating systems.
  • The trade-offs of time.sleep vs. scheduled-job libraries.
  • How to persist reminders in JSON so they survive restarts.
  • How to background the process so a closed terminal does not kill your reminders.
  • Python 3.6 or above.
  • A code editor or IDE.
  • Notifications enabled for Python on your OS.
install
pip install plyer

For native macOS notifications with sound, install pync instead:

mac
pip install pync
diagram how the pieces call each other mermaid
Derived from projects/beginners/simplereminderapp.py by parsing it, not by hand. Arrows are calls between the file's own functions and methods; library calls are left out, and only calls the parser could resolve with certainty are shown.
  1. Create folder simple-reminder-app.
  2. Inside, create simplereminderapp.py.
Simple Reminder App pch.viewSource
Simple Reminder App
# Simple Reminder App

# Reminder App For Windows
# Import Modules
import time
import datetime
from plyer import notification

# Defining Functions
def print_pause(message_to_print):
    print(message_to_print)
    time.sleep(2)
    
def reminder():
    print_pause("What would you like to be reminded about?")
    reminder = input("Enter your reminder: ")
    print_pause("In how many minutes would you like to be reminded?")
    minutes = input("Enter minutes: ")
    print_pause("Reminder set!")
    time.sleep(int(minutes) * 60)
    notification.notify(
        title = "Reminder",
        message = reminder,
        timeout = 10
    )
    print_pause("Reminder: " + reminder)
    print_pause("Reminder Completed!")
    play_again()
    
def play_again():
    print_pause("Would you like to set another reminder?")
    while True:
        choice = input("Enter yes or no: ")
        if choice == "yes":
            print_pause("Great!")
            reminder()
            break
        elif choice == "no":
            print_pause("Thank you for using the Reminder App!")
            break
        else:
            print_pause("Please enter yes or no: ")
            
def intro():
    print_pause("Welcome to the Reminder App!")
    print_pause("This app will remind you about anything you want!")
    print_pause("Let's get started!")
    reminder()
    
# Calling Functions
intro()


# Reminder App For Mac
# import time
# import datetime
# import pync

# # Defining Functions
# def print_pause(message_to_print):
#     print(message_to_print)
#     time.sleep(2)
    
# def reminder():
#     print_pause("What would you like to be reminded about?")
#     reminder = input("Enter your reminder: ")
#     print_pause("In how many minutes would you like to be reminded?")
#     minutes = input("Enter minutes: ")
#     print_pause("Reminder set!")
#     time.sleep(int(minutes) * 60)
#     pync.notify(reminder, title="Reminder", sound="default")
#     print_pause("Reminder: " + reminder)
#     print_pause("Reminder Completed!")
#     play_again()
    
# def play_again():
#     print_pause("Would you like to set another reminder?")
#     while True:
#         choice = input("Enter yes or no: ")
#         if choice == "yes":
#             print_pause("Great!")
#             reminder()
#             break
#         elif choice == "no":
#             print_pause("Thank you for using the Reminder App!")
#             break
#         else:
#             print_pause("Please enter yes or no: ")
            
# def intro():
#     print_pause("Welcome to the Reminder App!")
#     print_pause("This app will remind you about anything you want!")
#     print_pause("Let's get started!")
#     reminder()
    
# # Calling Functions
# intro()
command
C:\Users\Your Name\simple-reminder-app> python simplereminderapp.py
Welcome to the Reminder App!
What would you like to be reminded about? Call mom
In how many minutes? 2
Reminder set!
# … 2 minutes pass …
# Desktop notification pops up: "Reminder: Call mom"
Reminder Completed!
Would you like to set another reminder? (y/n) n
Thanks!
simplereminderapp.py
import time
from plyer import notification
  • time.sleep(seconds) pauses execution.
  • notification.notify(...) pops a desktop alert.
simplereminderapp.py
def print_pause(message, seconds=1):
    print(message)
    time.sleep(seconds)

Small pauses between prompts make the CLI feel less robotic.

simplereminderapp.py
def reminder():
    msg = input("What would you like to be reminded about? ")
    while True:
        raw = input("In how many minutes? ")
        try:
            minutes = float(raw)
            if minutes <= 0:
                raise ValueError("must be positive")
            break
        except ValueError:
            print("Please enter a positive number.")
    print_pause("Reminder set!")
    time.sleep(minutes * 60)
    notification.notify(title="Reminder", message=msg, timeout=10)
  • The while True loop is the canonical input validation pattern.
  • time.sleep(minutes * 60) blocks until the time arrives. Simple, but the script is unresponsive while it sleeps — a single typo in the message is unfixable.
simplereminderapp.py
def play_again():
    while True:
        ans = input("Set another reminder? (y/n) ").strip().lower()
        if ans in ("y", "yes"):
            return reminder()
        if ans in ("n", "no"):
            print("Thanks!")
            return
        print("Please answer y or n.")

The simple version blocks until the timer fires. While sleep(120) runs:

  • You cannot add a second reminder.
  • You cannot cancel it.
  • If the laptop sleeps, the timer pauses too — so a 30-minute reminder might fire 90 minutes late.

For a single quick reminder, this is fine. For anything serious, use a scheduler.

Use threads so each reminder runs independently:

multi.py
import threading, time
from plyer import notification
 
def fire_later(message: str, seconds: float):
    time.sleep(seconds)
    notification.notify(title="Reminder", message=message, timeout=10)
 
def add_reminder(message: str, minutes: float):
    t = threading.Thread(target=fire_later, args=(message, minutes * 60), daemon=True)
    t.start()
    print(f"Scheduled '{message}' in {minutes} minutes.")
 
while True:
    cmd = input("> ").strip()
    if cmd == "quit":
        break
    if cmd:
        msg, mins = cmd.rsplit(" ", 1)
        add_reminder(msg, float(mins))

Now you can keep adding reminders while previous ones wait. daemon=True lets the program exit even if reminders are still pending.

“At 14:30” is often more useful than “in 90 minutes”:

at_time.py
from datetime import datetime, timedelta
def schedule_at(hhmm: str, message: str):
    h, m = map(int, hhmm.split(":"))
    now = datetime.now()
    target = now.replace(hour=h, minute=m, second=0, microsecond=0)
    if target <= now:
        target += timedelta(days=1)
    seconds = (target - now).total_seconds()
    add_reminder(message, seconds / 60)

The thread approach forgets everything on restart. Persist to JSON:

persist.py
import json, pathlib, time
STORE = pathlib.Path("reminders.json")
 
def load():
    return json.loads(STORE.read_text()) if STORE.exists() else []
 
def save(reminders):
    STORE.write_text(json.dumps(reminders))
 
# at startup, re-schedule each not-yet-fired reminder:
for r in load():
    delay = r["due_ts"] - time.time()
    if delay > 0:
        add_reminder(r["message"], delay / 60)

Store entries as {"message": "...", "due_ts": <unix_ts>} so absolute time is preserved.

OSWhat worksNote
Windows 10/11plyer.notificationUses Windows Toast notifications.
macOSpync (with sound)plyer works but no sound by default.
Linuxplyer (uses notify-send)Requires notify-send installed (sudo apt install libnotify-bin).

A graceful fallback to terminal output:

fallback.py
try:
    notification.notify(title="Reminder", message=msg, timeout=10)
except Exception:
    print(f"\a*** REMINDER: {msg} ***\a")    # \a rings the terminal bell
ProblemCauseFix
Notification never appearsOS notifications muted, or Focus Assist onCheck OS notification settings
ModuleNotFoundError: plyerNot installed in the active interpreterpip install plyer
Timer way off after laptop waketime.sleep pauses during sleepUse datetime comparisons in a loop, or use schedule
ValueError: could not convert string to floatUser typed “five”Validate input in a loop
Process killed when terminal closesForeground scriptRun with nohup, pythonw.exe, or as a service
recur.py
import schedule, time
schedule.every().day.at("09:00").do(fire, "Take vitamins")
while True:
    schedule.run_pending(); time.sleep(30)

On fire, show a notification with a 5-minute snooze option. plyer itself does not support actions; use win10toast-click (Windows) or pync (macOS).

A small window with a Message field, an Entry for minutes, and a “Schedule” button. See Basic Music Player for the GUI pattern.

Use pystray to put a tray icon with “Add reminder” and “Quit” menu entries — no console window needed.

Use pyttsx3 so the reminder is spoken aloud:

speak.py
import pyttsx3
engine = pyttsx3.init(); engine.say(msg); engine.runAndWait()

Save reminders to a Firebase/Firestore document so they sync across devices.

pip install dateparser lets you type “tomorrow at 9am” or “in 2 hours”:

natural.py
import dateparser
target = dateparser.parse("tomorrow at 9am")

A bot that accepts /remind 30 buy bread from your phone and pings you back when due.

Pull events from Google Calendar via API; surface them as desktop reminders 5 minutes before each event.

A “/pomodoro” command that fires “Time to break!” every 25 minutes and “Back to work!” every 5.

So your laptop’s terminal is not always open:

  • Linux: create a systemd user service that runs the script on boot.
  • macOS: add a launchd plist with RunAtLoad.
  • Windows: save as .pyw and add a shortcut to shell:startup, or use NSSM to register as a service.
  • Medication / hydration reminders.
  • Meeting and standup pings.
  • Pomodoro timer for focused work.
  • Calendar-driven alerts beyond what your OS notification center handles.
  • “Brew tea in 4 minutes” — the original killer app.
  • Time handlingtime.sleep, datetime, scheduling libraries.
  • Concurrency basics — threads for parallel timers.
  • Cross-platform UX — when an API silently falls short on one OS.
  • Persistence — JSON-backed state so the app survives restarts.
  • Operational thinking — what does it mean to keep running after you close the terminal?
  • Implement multi-reminder threads above.
  • Add absolute-time parsing and persistent storage.
  • Wrap with a Tkinter GUI or a system-tray icon.
  • Use dateparser for natural-language times.
  • Connect to Google Calendar for event-driven reminders.
  • See Basic Alarm Clock for the polling-loop variant.

You built a real reminder tool, learned why time.sleep is not the right scheduler, and have a path to a polished tray app. Notification systems, schedulers, and tiny daemons underlie a lot of practical Python work. Full source on GitHub. Explore more productivity projects on Python Central Hub.

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading