A reminder feature looks local on the screen. Choose a time, choose a channel, save it.
Behind that form is a small distributed system. The scheduler, queue, notification provider, device, user account and application state can all disagree or fail independently.
The important engineering decision is not how to make failure impossible. It is how to make each failure explicit, recoverable and safe to retry.
Delivery is a pipeline
In the web application, due reminders are discovered by scheduled work. Delivery is queued rather than performed inside the scheduler. The job then sends through the channels the person selected.
That separation matters:
- Scheduling can keep discovering work without waiting for an external provider.
- Delivery failures can be retried and observed independently.
- One slow channel does not have to hold every other reminder hostage.
It also introduces questions about duplicate work, expired subscriptions and what “sent” means when a provider accepts a request but the device never displays it.
Warning (A useful distinction)
“The provider accepted it” and “the person received it” are different facts. A system should not claim the second when it only knows the first.
Available channels change
Email is always available. Discord and Telegram depend on connected accounts. Native push depends on a registered device token. Browser push can remain usable because the website owns that subscription, even though the native application must never try to manage it.
The API is authoritative for that availability. The client does not maintain its own list and hope it stays correct.
When an integration is disconnected, it must also be removed from default channels. Otherwise the system preserves a configuration that can no longer succeed.
This is the kind of detail that turns a settings screen into a dependable product boundary.
Actions need exactly-once behaviour
Native notifications can offer actions such as completing a reminder, ending a recurring series or moving it to tomorrow.
The operating system may deliver that response while the application is open, backgrounded or starting from a terminated state. The application must wait for an authenticated session, route the action to the correct API operation, update cached state and clear the response only after successful handling.
Clearing too early loses the action. Processing without a guard can repeat it. Registering several global handlers makes both outcomes harder to predict.
The solution is not a clever event bus. It is one explicit owner for foreground handling, one module-level background task where the platform requires it, and a shared definition of success.
Recurrence changes the meaning of done
For a one-off reminder, completion is simple. For a recurring reminder, it can mean at least two things:
- complete this occurrence and advance the schedule;
- stop the series entirely.
Those actions must have different names and different server behaviour. A single ambiguous “done” button would push an important distinction onto guesswork.
This is where product language and backend modelling meet. If the interface cannot explain the action clearly, the API should not hide the ambiguity behind one endpoint.
Quiet hours are not just two times
Do Not Disturb settings are wall-clock values in the account timezone. An end time before the start represents an overnight window. Equal start and end times are invalid because they do not communicate whether quiet mode is never active or always active.
Again, the form appears simple. The durable rule has to survive timezone changes, overnight boundaries and every channel that consumes it.
Designing for partial failure
The strongest version of the reminder system is not the one with the longest provider list. It is the one that can answer:
- Who owns this state?
- Can this operation run twice safely?
- What will the user see if only half of it succeeds?
- Can we observe and retry the failure?
- Are we describing what we know, or what we hope happened?
Those questions are useful far beyond notifications. Reminders simply make it impossible to avoid them.