Should I have a single cron job that loops the users, or one for each user?

Run a single cron, it’s much more manageable and more stable, and performance shouldn’t be an issue since you do 99% of the work of selecting which user should be notified in MySQL, which will be extremely fast.

I’d use a table to save each day (and each user on that day) that my cron has successfully run for, to make it more resilient. Plan for failure, not for success, because it will fail at some point, and the importance is how bad that failure will be and how much time it will take you to fix the fallout.

Server crashes right before midnight and is rebooted at 00:05? No problem, just run the job manually, or wait till the next day, it’ll run two days in one run!

Server crashes while executing the cron? No worries, the next time it runs, it’ll pick up right where it stopped last time.