Skip to main content
← Blog

Why Weekly Routine Notifications Stopped Working

VauDium ·

Daily worked fine. Weekly didn't. The cause was a single empty field.

Why Weekly Routine Notifications Stopped Working

Fecit has a routine feature. “Take medicine every day at 8am.” “Prep for meeting every Monday at 9am.” At the scheduled time, it automatically creates a task and sends a push notification.

One day we noticed the weekly notifications weren’t arriving. Daily ones worked fine. Only weekly was broken.

The System Was Running

The server was healthy. The background job that checks every minute — “are there any routines that should fire right now?” — was running without interruption. Daily routines proved the entire pipeline worked: the job fires, the database is queried, the push notification is sent. Everything was connected.

The only difference between daily and weekly: weekly has a weekday condition. “Match the minute AND the day of the week.” Since minute matching was proven by daily routines, the weekday matching had to be the problem.

We Opened the Database

We looked at the routine data directly. 15 weekly routines. Checked the weekday field on each one.

Every single one was empty.

What should have been “Monday” was blank. “Wednesday” was blank. “Saturday,” “Thursday” — all 15 were None.

An empty weekday never matches any day. The system checks every minute, faithfully, but blank doesn’t equal Monday. Blank doesn’t equal anything. That’s why not a single weekly routine had ever fired.

Why Was It Empty?

When a user creates a routine, the app is supposed to send the weekday to the server. “Monday.” The server stores it as received. It trusted the app to send the right value.

The problem: in a certain registration flow, the weekday value was being dropped. Not sent. The server received nothing, stored nothing, and no error was raised. Silent failure.

Daily routines don’t need a weekday, so this bug stayed hidden. You’d only discover it by registering a weekly routine, waiting for the right day to pass, and noticing the notification never came.

A Second Problem, Found While Fixing the First

We thought filling in the blanks would be the end of it. But the moment we asked “what value should we fill in?” — the timezone question appeared.

The background job runs on UTC, the global standard time. Every minute it calculates the current UTC time, extracts the weekday, and queries the database.

Korea is 9 hours ahead of UTC. Monday 8am in Korea is Sunday 11pm in UTC. When the job asks “are there any routines for Sunday?” — a routine registered as “Monday” by a Korean user wouldn’t match.

Fortunately, the start time was already stored in UTC. When the app sends a time, JavaScript’s Date automatically converts it. Korean Monday 8am becomes UTC Sunday 11pm. Minutes are stored in UTC too, which is why minute matching worked perfectly.

But the weekday alone was using the app’s local value. Even if it hadn’t been empty, routines set between Korean midnight and 9am would have had their weekday off by one day — UTC and local disagree during those hours.

The Fix: Let the Server Decide

Two changes.

First, we stopped relying on the app to send the weekday. The server already has the routine’s start time in UTC. It can calculate the weekday itself. Whatever the app sends, the server determines the day from information it already possesses. UTC time yields UTC weekday, which matches the job’s UTC weekday perfectly.

Second, we had to fix the 15 existing weekly routines. We wrote a migration script that calculated the UTC weekday from each routine’s start time and filled in the blanks.

Running the script, all 15 routines were updated. Blank became Monday (0), Saturday (5), Thursday (3) — each finding its rightful value. From the next cycle onward, notifications started arriving as they should.

Looking Back

The cause was simple. One empty field in the database. But finding it meant tracing the entire notification pipeline. App to server, server to job, job back to server, server to push notification service.

It was harder to find because daily routines worked perfectly. When a system is “almost” working, the part that’s “slightly” broken is the hardest to spot. The server is running, the job fires every minute, the query syntax is correct — and yet, no results.

Two lessons.

First: if the server can calculate a value, the server should calculate it. Don’t wait for the app to send it. This time the value was missing. Next time it could be in the wrong timezone.

Second: watch for what’s broken behind what works. Daily routines working flawlessly masked the weekly routine problem. When everything breaks, you notice immediately. When only part of it breaks, it takes much longer.