Simple Reminder App
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 plyerplyer 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.sleeptime.sleepactually works (and when it is the wrong tool). - How
plyerplyerabstracts notification systems across operating systems. - The trade-offs of
time.sleeptime.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
- Python 3.6 or above.
- A code editor or IDE.
- Notifications enabled for Python on your OS.
Install Dependencies
pip install plyerpip install plyerFor native macOS notifications with sound, install pyncpync instead:
pip install pyncpip install pyncGetting Started
Create the project
- Create folder
simple-reminder-appsimple-reminder-app. - Inside, create
simplereminderapp.pysimplereminderapp.py.
Write the code
Simple Reminder App
Source# 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()# 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
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!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
1. Imports
import time
from plyer import notificationimport time
from plyer import notificationtime.sleep(seconds)time.sleep(seconds)pauses execution.notification.notify(...)notification.notify(...)pops a desktop alert.
2. The pacing helper
def print_pause(message, seconds=1):
print(message)
time.sleep(seconds)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
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)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 Truewhile Trueloop is the canonical input validation pattern. time.sleep(minutes * 60)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
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.")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.sleeptime.sleep Problem
The simple version blocks until the timer fires. While sleep(120)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
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))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=Truedaemon=True lets the program exit even if reminders are still pending.
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)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
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)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>}{"message": "...", "due_ts": <unix_ts>} so absolute time is preserved.
Cross-Platform Notifications
| OS | What works | Note |
|---|---|---|
| Windows 10/11 | plyer.notificationplyer.notification | Uses Windows Toast notifications. |
| macOS | pyncpync (with sound) | plyerplyer works but no sound by default. |
| Linux | plyerplyer (uses notify-sendnotify-send) | Requires notify-sendnotify-send installed (sudo apt install libnotify-binsudo 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 belltry:
notification.notify(title="Reminder", message=msg, timeout=10)
except Exception:
print(f"\a*** REMINDER: {msg} ***\a") # \a rings the terminal bellCommon Mistakes
| Problem | Cause | Fix |
|---|---|---|
| Notification never appears | OS notifications muted, or Focus Assist on | Check OS notification settings |
ModuleNotFoundError: plyerModuleNotFoundError: plyer | Not installed in the active interpreter | pip install plyerpip install plyer |
| Timer way off after laptop wake | time.sleeptime.sleep pauses during sleep | Use datetimedatetime comparisons in a loop, or use scheduleschedule |
ValueError: could not convert string to floatValueError: could not convert string to float | User typed “five” | Validate input in a loop |
| Process killed when terminal closes | Foreground script | Run with nohupnohup, pythonw.exepythonw.exe, or as a service |
Variations to Try
1. Recurring reminders
import schedule, time
schedule.every().day.at("09:00").do(fire, "Take vitamins")
while True:
schedule.run_pending(); time.sleep(30)import schedule, time
schedule.every().day.at("09:00").do(fire, "Take vitamins")
while True:
schedule.run_pending(); time.sleep(30)2. Snooze button
On fire, show a notification with a 5-minute snooze option. plyerplyer itself does not support actions; use win10toast-clickwin10toast-click (Windows) or pyncpync (macOS).
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
Use pystraypystray to put a tray icon with “Add reminder” and “Quit” menu entries — no console window needed.
5. Voice reminder
Use pyttsx3pyttsx3 so the reminder is spoken aloud:
import pyttsx3
engine = pyttsx3.init(); engine.say(msg); engine.runAndWait()import pyttsx3
engine = pyttsx3.init(); engine.say(msg); engine.runAndWait()6. Cloud sync
Save reminders to a Firebase/Firestore document so they sync across devices.
7. Smart parsing
pip install dateparserpip install dateparser lets you type “tomorrow at 9am” or “in 2 hours”:
import dateparser
target = dateparser.parse("tomorrow at 9am")import dateparser
target = dateparser.parse("tomorrow at 9am")8. Telegram / Discord bot
A bot that accepts /remind 30 buy bread/remind 30 buy bread from your phone and pings you back when due.
9. Calendar integration
Pull events from Google Calendar via API; surface them as desktop reminders 5 minutes before each event.
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
So your laptop’s terminal is not always open:
- Linux: create a systemd user service that runs the script on boot.
- macOS: add a
launchdlaunchdplist withRunAtLoadRunAtLoad. - Windows: save as
.pyw.pywand add a shortcut toshell:startupshell:startup, or use NSSM to register as a service.
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
- Time handling —
time.sleeptime.sleep,datetimedatetime, 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
- Implement multi-reminder threads above.
- Add absolute-time parsing and persistent storage.
- Wrap with a Tkinter GUI or a system-tray icon.
- Use
dateparserdateparserfor natural-language times. - Connect to Google Calendar for event-driven reminders.
- See Basic Alarm Clock for the polling-loop variant.
Conclusion
You built a real reminder tool, learned why time.sleeptime.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.
If this helped you, consider buying me a coffee ☕
Buy me a coffeeWas this page helpful?
Let us know how we did
