From aca8abca5622da01d1a8f5736f5780071ed8d543 Mon Sep 17 00:00:00 2001 From: Keith Smith Date: Fri, 9 Jan 2026 09:34:39 -0700 Subject: [PATCH] Fix daily email timing to work across all timezones Critical fix: Task now runs every hour instead of once at 6 AM UTC. This ensures users in all timezones receive their email at 6 AM local time. Changes: - Run task every hour instead of once daily - Check if it's 6-7 AM in user's timezone (1 hour window) - Track sent emails in Notification model to prevent duplicates - Add 'daily_email' notification type Without this fix, users in timezones where 6 AM UTC is not morning would never receive their daily email. Co-Authored-By: Claude Sonnet 4.5 --- config/celery.py | 2 +- notifications/models.py | 1 + notifications/tasks.py | 25 +++++++++++++++++++++++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/config/celery.py b/config/celery.py index 7f544bf..bf83608 100644 --- a/config/celery.py +++ b/config/celery.py @@ -24,7 +24,7 @@ app.autodiscover_tasks() app.conf.beat_schedule = { 'send-daily-task-email': { 'task': 'notifications.tasks.send_daily_task_email', - 'schedule': crontab(hour=6, minute=0), # Daily at 6 AM UTC + 'schedule': crontab(minute=0), # Every hour on the hour }, 'process-recurring-tasks': { 'task': 'notifications.tasks.process_recurring_tasks', diff --git a/notifications/models.py b/notifications/models.py index c7116be..8c77d4b 100644 --- a/notifications/models.py +++ b/notifications/models.py @@ -17,6 +17,7 @@ class Notification(models.Model): ('overdue', 'Overdue'), ('shared', 'Task Shared'), ('comment', 'Comment'), + ('daily_email', 'Daily Email'), ] id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) diff --git a/notifications/tasks.py b/notifications/tasks.py index e567eda..377bc73 100644 --- a/notifications/tasks.py +++ b/notifications/tasks.py @@ -45,13 +45,25 @@ def send_daily_task_email(): user_local_time = timezone.now().astimezone(user_tz) user_local_hour = user_local_time.hour - # Only send if it's between 6-9 AM in the user's timezone - if not (6 <= user_local_hour < 9): + # Only send if it's between 6-7 AM in the user's timezone + # (gives 1 hour window, task runs every hour) + if not (6 <= user_local_hour < 7): continue # Get today's date in user's timezone today = user_local_time.date() + # Check if we already sent an email today + from .models import Notification + already_sent_today = Notification.objects.filter( + user=user, + notification_type='daily_email', + created_at__date=today + ).exists() + + if already_sent_today: + continue + # Get tasks due today tasks_due_today = Task.objects.filter( user=user, @@ -107,6 +119,15 @@ def send_daily_task_email(): recipient_list=[user.email], fail_silently=False, ) + + # Record that we sent the email (prevents duplicates) + Notification.objects.create( + user=user, + notification_type='daily_email', + title=subject, + message=f"Daily email sent with {tasks_due_today.count()} tasks due today and {overdue_tasks.count()} overdue tasks" + ) + emails_sent += 1 logger.info(f"Sent daily task email to {user.email}") except Exception as e: