Skip to content

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.sleep actually works (and when it is the wrong tool).
  • How plyerplyer abstracts notification systems across operating systems.
  • The trade-offs of time.sleeptime.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.

Prerequisites

  • Python 3.6 or above.
  • A code editor or IDE.
  • Notifications enabled for Python on your OS.

Install Dependencies

install
pip install plyer
install
pip install plyer

For native macOS notifications with sound, install pyncpync instead:

mac
pip install pync
mac
pip install pync

Getting Started

Create the project

  1. Create folder simple-reminder-appsimple-reminder-app.
  2. Inside, create simplereminderapp.pysimplereminderapp.py.

Write the code

Simple Reminder AppSource
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()
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()

Run it

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!
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!

Step-by-Step Explanation

1. Imports

simplereminderapp.py
import time
from plyer import notification
simplereminderapp.py
import time
from plyer import notification
  • time.sleep(seconds)time.sleep(seconds) pauses execution.
  • notification.notify(...)notification.notify(...) pops a desktop alert.

2. The pacing helper

simplereminderapp.py
def print_pause(message, seconds=1):
    print(message)
    time.sleep(seconds)
simplereminderapp.py
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

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)
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 Truewhile True loop 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

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.")
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 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:

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))
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=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”:

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)
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)

Persistent Reminders

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)
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>}{"message": "...", "due_ts": <unix_ts>} so absolute time is preserved.

Cross-Platform Notifications

OSWhat worksNote
Windows 10/11plyer.notificationplyer.notificationUses Windows Toast notifications.
macOSpyncpync (with sound)plyerplyer works but no sound by default.
Linuxplyerplyer (uses notify-sendnotify-send)Requires notify-sendnotify-send installed (sudo apt install libnotify-binsudo 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
fallback.py
try:
    notification.notify(title="Reminder", message=msg, timeout=10)
except Exception:
    print(f"\a*** REMINDER: {msg} ***\a")    # \a rings the terminal bell

Common Mistakes

ProblemCauseFix
Notification never appearsOS notifications muted, or Focus Assist onCheck OS notification settings
ModuleNotFoundError: plyerModuleNotFoundError: plyerNot installed in the active interpreterpip install plyerpip install plyer
Timer way off after laptop waketime.sleeptime.sleep pauses during sleepUse datetimedatetime comparisons in a loop, or use scheduleschedule
ValueError: could not convert string to floatValueError: could not convert string to floatUser typed “five”Validate input in a loop
Process killed when terminal closesForeground scriptRun with nohupnohup, pythonw.exepythonw.exe, or as a service

Variations to Try

1. Recurring reminders

recur.py
import schedule, time
schedule.every().day.at("09:00").do(fire, "Take vitamins")
while True:
    schedule.run_pending(); time.sleep(30)
recur.py
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:

speak.py
import pyttsx3
engine = pyttsx3.init(); engine.say(msg); engine.runAndWait()
speak.py
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”:

natural.py
import dateparser
target = dateparser.parse("tomorrow at 9am")
natural.py
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 launchdlaunchd plist with RunAtLoadRunAtLoad.
  • Windows: save as .pyw.pyw and add a shortcut to shell: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 handlingtime.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 dateparserdateparser for 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 coffee

Was this page helpful?

Let us know how we did