Simple Reminder App
Abstract
Section titled “Abstract”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.sleepactually works (and when it is the wrong tool). - How
plyerabstracts notification systems across operating systems. - The trade-offs of
time.sleepvs. 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.
Prerequisites
Section titled “Prerequisites”- Python 3.6 or above.
- A code editor or IDE.
- Notifications enabled for Python on your OS.
Install Dependencies
Section titled “Install Dependencies”pip install plyerFor native macOS notifications with sound, install pync instead:
pip install pyncflowchart TD n0(["script start"]) n107["intro()"] n108["play_again()"] n109["print_pause()"] n110["reminder()"] n0 --> n107 n107 --> n109 n107 --> n110 n108 --> n109 n108 --> n110 n110 --> n108 n110 --> n109
Getting Started
Section titled “Getting Started”Create the project
Section titled “Create the project”- Create folder
simple-reminder-app. - Inside, create
simplereminderapp.py.
Write the code
Section titled “Write the code”Simple Reminder App
pch.viewSource# 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() Run it
Section titled “Run it”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!Step-by-Step Explanation
Section titled “Step-by-Step Explanation”1. Imports
Section titled “1. Imports”import time
from plyer import notificationtime.sleep(seconds)pauses execution.notification.notify(...)pops a desktop alert.
2. The pacing helper
Section titled “2. The pacing helper”def print_pause(message, seconds=1):
print(message)
time.sleep(seconds)Small pauses between prompts make the CLI feel less robotic.
3. Collect the reminder
Section titled “3. Collect the reminder”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 Trueloop 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.
4. Replay
Section titled “4. Replay”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 time.sleep Problem
Section titled “The time.sleep Problem”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.
Multi-Reminder Version
Section titled “Multi-Reminder Version”Use threads so each reminder runs independently:
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.
Absolute-Time Reminders
Section titled “Absolute-Time Reminders”“At 14:30” is often more useful than “in 90 minutes”:
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)Persistent Reminders
Section titled “Persistent Reminders”The thread approach forgets everything on restart. Persist to JSON:
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.
Cross-Platform Notifications
Section titled “Cross-Platform Notifications”| OS | What works | Note |
|---|---|---|
| Windows 10/11 | plyer.notification | Uses Windows Toast notifications. |
| macOS | pync (with sound) | plyer works but no sound by default. |
| Linux | plyer (uses notify-send) | Requires notify-send installed (sudo apt install libnotify-bin). |
A graceful fallback to terminal output:
try:
notification.notify(title="Reminder", message=msg, timeout=10)
except Exception:
print(f"\a*** REMINDER: {msg} ***\a") # \a rings the terminal bellCommon Mistakes
Section titled “Common Mistakes”| Problem | Cause | Fix |
|---|---|---|
| Notification never appears | OS notifications muted, or Focus Assist on | Check OS notification settings |
ModuleNotFoundError: plyer | Not installed in the active interpreter | pip install plyer |
| Timer way off after laptop wake | time.sleep pauses during sleep | Use datetime comparisons in a loop, or use schedule |
ValueError: could not convert string to float | User typed “five” | Validate input in a loop |
| Process killed when terminal closes | Foreground script | Run with nohup, pythonw.exe, or as a service |
Variations to Try
Section titled “Variations to Try”1. Recurring reminders
Section titled “1. Recurring reminders”import schedule, time
schedule.every().day.at("09:00").do(fire, "Take vitamins")
while True:
schedule.run_pending(); time.sleep(30)2. Snooze button
Section titled “2. Snooze button”On fire, show a notification with a 5-minute snooze option. plyer itself does not support actions; use win10toast-click (Windows) or pync (macOS).
3. GUI with Tkinter
Section titled “3. GUI with Tkinter”A small window with a Message field, an Entry for minutes, and a “Schedule” button. See Basic Music Player for the GUI pattern.
4. System-tray app
Section titled “4. System-tray app”Use pystray to put a tray icon with “Add reminder” and “Quit” menu entries — no console window needed.
5. Voice reminder
Section titled “5. Voice reminder”Use pyttsx3 so the reminder is spoken aloud:
import pyttsx3
engine = pyttsx3.init(); engine.say(msg); engine.runAndWait()6. Cloud sync
Section titled “6. Cloud sync”Save reminders to a Firebase/Firestore document so they sync across devices.
7. Smart parsing
Section titled “7. Smart parsing”pip install dateparser lets you type “tomorrow at 9am” or “in 2 hours”:
import dateparser
target = dateparser.parse("tomorrow at 9am")8. Telegram / Discord bot
Section titled “8. Telegram / Discord bot”A bot that accepts /remind 30 buy bread from your phone and pings you back when due.
9. Calendar integration
Section titled “9. Calendar integration”Pull events from Google Calendar via API; surface them as desktop reminders 5 minutes before each event.
10. Pomodoro mode
Section titled “10. Pomodoro mode”A “/pomodoro” command that fires “Time to break!” every 25 minutes and “Back to work!” every 5.
Running as a Background Service
Section titled “Running as a Background Service”So your laptop’s terminal is not always open:
- Linux: create a systemd user service that runs the script on boot.
- macOS: add a
launchdplist withRunAtLoad. - Windows: save as
.pywand add a shortcut toshell:startup, or use NSSM to register as a service.
Real-World Applications
Section titled “Real-World Applications”- 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.
Educational Value
Section titled “Educational Value”- Time handling —
time.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?
Next Steps
Section titled “Next Steps”- Implement multi-reminder threads above.
- Add absolute-time parsing and persistent storage.
- Wrap with a Tkinter GUI or a system-tray icon.
- Use
dateparserfor natural-language times. - Connect to Google Calendar for event-driven reminders.
- See Basic Alarm Clock for the polling-loop variant.
Conclusion
Section titled “Conclusion”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.coffeeCtapch.feedbackHeading
pch.feedbackSubheading