Nanny7 Claude Sonnet 4.5 commited on
Commit
eec399c
Β·
1 Parent(s): 31e7e07

feat: add email reminder system

Browse files

Backend changes:
- Add reminder_sent field to Todo model
- Create email service with Gmail SMTP support
- Create reminder service with hourly scheduler
- Integrate APScheduler in FastAPI lifespan
- Add Gmail configuration to settings
- Update todo API to return reminder_sent
- Create database migration for reminder_sent column

Frontend changes:
- Show reminder info in CreateTodoModal
- Add reminder indicator (πŸ””) in TodoList
- Add reminder_sent to Todo type interface

Features:
- Sends reminder emails 1 day before due date
- Includes full task details (title, description, due date, priority, tags)
- Uses Gmail SMTP for email delivery
- Runs hourly checks via APScheduler
- Tracks sent reminders to avoid duplicates

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

EMAIL-REMINDERS-GUIDE.md ADDED
@@ -0,0 +1,358 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # πŸ“§ Email Reminder System - Setup Guide
2
+
3
+ ## βœ… Feature Complete!
4
+
5
+ Email reminders have been successfully added to your Todo App. Here's how to set them up.
6
+
7
+ ---
8
+
9
+ ## 🎯 What This Feature Does
10
+
11
+ - **Automatically sends reminder emails** to users 1 day before their task's due date
12
+ - **Sends to user's login email** (the email they used to sign up)
13
+ - **Only for tasks with due dates** set
14
+ - **Includes full task details** in the email:
15
+ - Task title
16
+ - Task description
17
+ - Due date and time
18
+ - Priority level (High/Medium/Low)
19
+ - Tags
20
+ - Time remaining
21
+
22
+ ---
23
+
24
+ ## πŸ“§ How to Setup Gmail SMTP
25
+
26
+ ### Step 1: Enable 2-Step Verification
27
+
28
+ 1. Go to https://myaccount.google.com/security
29
+ 2. Find "2-Step Verification" and enable it (if not already enabled)
30
+ 3. Follow the prompts to set it up
31
+
32
+ ### Step 2: Generate App Password
33
+
34
+ 1. On the same security page, search for "App Passwords"
35
+ 2. Click on it (you may need to sign in again)
36
+ 3. Select:
37
+ - **App**: Select "Mail"
38
+ - **Device**: Select "Other (Custom name)"
39
+ 4. Enter "Todo App" as the name
40
+ 5. Click **Generate**
41
+ 6. **Copy the 16-character password** (it will look like: `abcd efgh ijkl mnop`)
42
+ 7. **Save it securely** - you won't see it again!
43
+
44
+ ### Step 3: Add to Backend Environment Variables
45
+
46
+ **For Local Development:**
47
+
48
+ Edit `backend/.env` and add:
49
+ ```bash
50
+ GMAIL_EMAIL=your-email@gmail.com
51
+ GMAIL_APP_PASSWORD=your-16-char-app-password
52
+ ```
53
+
54
+ **For Hugging Face Space (Production):**
55
+
56
+ Add these secrets via Hugging Face API or web UI:
57
+ ```bash
58
+ # Via Hugging Face CLI
59
+ huggingface-cli login
60
+ # Then add secrets via web UI at:
61
+ # https://huggingface.co/spaces/ammaraak/todo-app/settings
62
+ ```
63
+
64
+ Add these two secrets:
65
+ - `GMAIL_EMAIL`: Your Gmail address
66
+ - `GMAIL_APP_PASSWORD`: The 16-character app password
67
+
68
+ ---
69
+
70
+ ## πŸ—„οΈ Database Migration
71
+
72
+ ### Apply Migration to Add `reminder_sent` Column
73
+
74
+ **For Local Database:**
75
+ ```bash
76
+ cd backend
77
+ python -m alembic upgrade head
78
+ ```
79
+
80
+ **For Hugging Face Production Database:**
81
+ The migration will run automatically when the space rebuilds.
82
+
83
+ **What this does:**
84
+ - Adds `reminder_sent` column to `todos` table
85
+ - Sets default value to `False` for existing tasks
86
+ - Tracks whether reminder email has been sent for each task
87
+
88
+ ---
89
+
90
+ ## πŸš€ How to Run
91
+
92
+ ### Local Development
93
+
94
+ 1. **Install backend dependencies:**
95
+ ```bash
96
+ cd backend
97
+ pip install -r requirements.txt
98
+ ```
99
+
100
+ 2. **Set environment variables in `.env`:**
101
+ ```bash
102
+ GMAIL_EMAIL=your-email@gmail.com
103
+ GMAIL_APP_PASSWORD=your-app-password
104
+ NEON_DATABASE_URL=your-database-url
105
+ JWT_SECRET=your-jwt-secret
106
+ # ... other env vars
107
+ ```
108
+
109
+ 3. **Apply database migration:**
110
+ ```bash
111
+ python -m alembic upgrade head
112
+ ```
113
+
114
+ 4. **Run the backend:**
115
+ ```bash
116
+ python -m uvicorn src.main:app --reload --port 8801
117
+ ```
118
+
119
+ 5. **Check startup logs:**
120
+ Look for:
121
+ ```
122
+ βœ… Reminder scheduler started (runs every hour)
123
+ ```
124
+
125
+ If you see:
126
+ ```
127
+ ⚠️ Reminder scheduler disabled (Gmail not configured)
128
+ ```
129
+ Then your Gmail credentials are missing or incorrect.
130
+
131
+ ### Production (Hugging Face Space)
132
+
133
+ 1. **Add secrets to Hugging Face Space:**
134
+ - `GMAIL_EMAIL`
135
+ - `GMAIL_APP_PASSWORD`
136
+ - `NEON_DATABASE_URL`
137
+ - `JWT_SECRET`
138
+ - `QWEN_API_KEY`
139
+
140
+ 2. **Push to Hugging Face:**
141
+ ```bash
142
+ cd hf-space
143
+ git add .
144
+ git commit -m "feat: add email reminder system"
145
+ git push
146
+ ```
147
+
148
+ 3. **Wait for rebuild** (~5 minutes)
149
+
150
+ 4. **Check logs:**
151
+ - Go to https://huggingface.co/spaces/ammaraak/todo-app
152
+ - Click "Logs" or "Settings"
153
+ - Look for "βœ… Reminder scheduler started"
154
+
155
+ ---
156
+
157
+ ## πŸ“± How to Use
158
+
159
+ ### For Users
160
+
161
+ 1. **Create a task with a due date:**
162
+ - Login to the app
163
+ - Click "Create New Todo"
164
+ - Fill in title, description
165
+ - **Select a due date**
166
+ - You'll see: "πŸ”” Reminder email will be sent 1 day before"
167
+
168
+ 2. **The reminder indicator:**
169
+ - In the todo list, tasks with due dates show a πŸ”” icon
170
+ - This means reminder is scheduled
171
+ - After reminder is sent, the icon disappears
172
+
173
+ 3. **Receive the email:**
174
+ - 1 day before the due date, check your email
175
+ - You'll get a beautiful HTML email with:
176
+ - Task title
177
+ - Full description
178
+ - Due date and time
179
+ - Priority level
180
+ - Tags
181
+ - Time remaining
182
+
183
+ ---
184
+
185
+ ## πŸ§ͺ Testing
186
+
187
+ ### Test 1: Manual Email Test
188
+
189
+ Create a test script `backend/test_email.py`:
190
+
191
+ ```python
192
+ from src.services.email_service import email_service
193
+ from datetime import datetime, timedelta
194
+
195
+ # Test email sending
196
+ test_due_date = datetime.utcnow() + timedelta(hours=24)
197
+
198
+ success = email_service.send_reminder(
199
+ to_email="your-test-email@gmail.com",
200
+ task_title="Test Task - Email Reminder",
201
+ task_description="This is a test to verify email reminders are working!",
202
+ due_date=test_due_date,
203
+ priority="high",
204
+ tags=["test"],
205
+ task_id="test-123"
206
+ )
207
+
208
+ print(f"Email sent: {success}")
209
+ ```
210
+
211
+ Run it:
212
+ ```bash
213
+ cd backend
214
+ python test_email.py
215
+ ```
216
+
217
+ Check your inbox!
218
+
219
+ ### Test 2: Full Integration Test
220
+
221
+ 1. **Create a task due tomorrow:**
222
+ - Due date: Tomorrow's date
223
+ - Title: "Test Reminder Task"
224
+
225
+ 2. **Wait for hourly check** (or manually trigger):
226
+ ```python
227
+ from src.services.reminder_service import check_and_send_reminders
228
+ from src.core.database import get_session
229
+
230
+ session = next(get_session())
231
+ result = check_and_send_reminders(session)
232
+ print(result) # Should show {"sent": 1, ...}
233
+ ```
234
+
235
+ 3. **Check your email** - you should receive reminder immediately
236
+
237
+ 4. **Verify in database:**
238
+ ```python
239
+ # Check that reminder_sent is now True
240
+ ```
241
+
242
+ ---
243
+
244
+ ## πŸ“Š Email Template Preview
245
+
246
+ Here's what the email looks like:
247
+
248
+ ```
249
+ πŸ”” Task Reminder
250
+
251
+ Your task is due tomorrow:
252
+
253
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
254
+ β”‚ Test Reminder Task β”‚
255
+ β”‚ β”‚
256
+ β”‚ This is a test to verify email... β”‚
257
+ β”‚ β”‚
258
+ β”‚ πŸ“… Due Date: January 29, 2026 β”‚
259
+ β”‚ ⏰ Time Remaining: 24 hours β”‚
260
+ β”‚ 🎯 Priority: πŸ”΄ High β”‚
261
+ β”‚ 🏷️ Tags: test β”‚
262
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
263
+
264
+ πŸ’‘ Tip: Make sure to complete your task on time!
265
+
266
+ ---
267
+ This is an automated reminder from your Todo App.
268
+ Task ID: test-123
269
+ ```
270
+
271
+ ---
272
+
273
+ ## πŸ” Troubleshooting
274
+
275
+ ### Scheduler Not Starting
276
+
277
+ **Problem:** `⚠️ Reminder scheduler disabled (Gmail not configured)`
278
+
279
+ **Solution:**
280
+ 1. Check `.env` file has `GMAIL_EMAIL` and `GMAIL_APP_PASSWORD`
281
+ 2. Restart the backend server
282
+ 3. Check for typos in env var names
283
+
284
+ ### Emails Not Sending
285
+
286
+ **Problem:** No emails received
287
+
288
+ **Solutions:**
289
+ 1. **Check app password:**
290
+ - Make sure you're using the 16-character app password, NOT your regular password
291
+ - Regenerate the app password if needed
292
+
293
+ 2. **Check Gmail settings:**
294
+ - Make sure 2-Step Verification is enabled
295
+ - Make sure "Less secure app access" is NOT blocked
296
+
297
+ 3. **Check logs:**
298
+ ```bash
299
+ # Look for error messages in backend console
300
+ # Common errors:
301
+ # - "Authentication failed" β†’ Wrong password
302
+ # - "Connection refused" β†’ Network/firewall issue
303
+ ```
304
+
305
+ ### Migration Failed
306
+
307
+ **Problem:** `Can't locate revision identified by '29f774bde39a'`
308
+
309
+ **Solution:**
310
+ The migration was already created manually at `backend/alembic/versions/002_add_reminder_sent.py`. Just run:
311
+ ```bash
312
+ cd backend
313
+ python -m alembic upgrade head
314
+ ```
315
+
316
+ ---
317
+
318
+ ## πŸ“ Summary
319
+
320
+ ### Files Created/Modified
321
+
322
+ **Backend:**
323
+ - βœ… `backend/src/models/todo.py` - Added `reminder_sent` field
324
+ - βœ… `backend/src/schemas/todo.py` - Updated response schema
325
+ - βœ… `backend/src/services/email_service.py` - NEW email sending service
326
+ - βœ… `backend/src/services/reminder_service.py` - NEW reminder checker
327
+ - βœ… `backend/src/core/config.py` - Added Gmail config
328
+ - βœ… `backend/src/main.py` - Integrated scheduler
329
+ - βœ… `backend/src/api/todos.py` - Updated to return `reminder_sent`
330
+ - βœ… `backend/requirements.txt` - Added APScheduler
331
+ - βœ… `backend/alembic/versions/002_add_reminder_sent.py` - NEW migration
332
+
333
+ **Frontend:**
334
+ - βœ… `frontend/src/components/dashboard/CreateTodoModal.tsx` - Shows reminder info
335
+ - βœ… `frontend/src/components/dashboard/TodoList.tsx` - Shows reminder indicator
336
+ - βœ… `frontend/src/types/index.ts` - Added `reminder_sent` to Todo interface
337
+
338
+ ---
339
+
340
+ ## πŸŽ‰ You're All Set!
341
+
342
+ Once you:
343
+ 1. βœ… Generate Gmail app password
344
+ 2. βœ… Add to `.env` or Hugging Face secrets
345
+ 3. βœ… Run database migration
346
+ 4. βœ… Restart the backend
347
+
348
+ Your app will automatically send reminder emails 1 day before tasks are due!
349
+
350
+ **The scheduler runs every hour**, checking for tasks due in ~24 hours and sending reminders.
351
+
352
+ ---
353
+
354
+ *Implementation Date: 2026-01-28*
355
+ *Feature: Email Reminders for Tasks*
356
+ *Trigger: 1 day before due date*
357
+ *Service: Gmail SMTP*
358
+ *Scheduler: APScheduler (hourly)*
MOBILE-FIXES.md ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # πŸ“± Mobile Responsiveness Fixes - DEPLOYED
2
+
3
+ ## Status: DEPLOYED to GitHub βœ…
4
+
5
+ ---
6
+
7
+ ## 🎯 Problem
8
+
9
+ **Issue:** "chat mobile responsive ni ha"
10
+ Chat interface was not responsive on mobile devices - fixed positioning didn't work well on small screens.
11
+
12
+ ---
13
+
14
+ ## βœ… What Was Fixed
15
+
16
+ ### **1. AIChatPanel.tsx** - Full Width on Mobile
17
+ **Before:**
18
+ ```tsx
19
+ className="fixed bottom-20 right-4 ... w-full md:max-w-md md:h-600px"
20
+ ```
21
+ **After:**
22
+ ```tsx
23
+ className="fixed bottom-20 right-4 left-4 md:left-auto md:right-6 w-auto md:w-full md:max-w-md h-[calc(100vh-80px)] md:h-auto"
24
+ ```
25
+
26
+ **Mobile:** `left-4 right-4` (full width with margins), `h-[calc(100vh-80px)]` (fit viewport)
27
+ **Desktop:** `md:left-auto md:right-6` (right side only), `md:h-auto` (fixed height)
28
+
29
+ ---
30
+
31
+ ### **2. AIChatButton.tsx** - Smaller Button on Mobile
32
+ **Before:**
33
+ ```tsx
34
+ className="fixed bottom-6 right-4 md:bottom-6 md:right-6"
35
+ // Button: w-16 h-16 (64px)
36
+ ```
37
+ **After:**
38
+ ```tsx
39
+ className="fixed bottom-20 right-4 z-50 md:bottom-6 md:right-6"
40
+ // Button: w-14 h-14 md:w-16 md:h-16
41
+ ```
42
+
43
+ **Mobile:** 14x14 (56px) at bottom-20 (avoids mobile elements)
44
+ **Desktop:** 16x16 (64px) at bottom-6
45
+
46
+ ---
47
+
48
+ ### **3. ChatInput.tsx** - Mobile-Friendly Sizing
49
+ **Before:**
50
+ ```tsx
51
+ className="flex gap-2 items-end p-4 ... text-base"
52
+ // Button: min-w-[60px]
53
+ ```
54
+ **After:**
55
+ ```tsx
56
+ className="flex gap-2 items-end p-3 md:p-4 ... text-sm md:text-base"
57
+ // Button: min-w-[40px] md:min-w-[60px]
58
+ ```
59
+
60
+ **Mobile:** Smaller padding (p-3), smaller text (text-sm), smaller button (40px)
61
+ **Desktop:** Normal padding (p-4), base text, larger button (60px)
62
+
63
+ ---
64
+
65
+ ## πŸš€ Deployment Status
66
+
67
+ ### **GitHub:** βœ… Pushed to `001-ai-assistant` branch
68
+ ```
69
+ https://github.com/ammarakk/Todo-App
70
+ ```
71
+
72
+ ### **Vercel:** πŸ”„ Auto-deploying from GitHub
73
+ Vercel will automatically build and deploy when it detects the new commit.
74
+
75
+ **Check deployment status:**
76
+ ```
77
+ https://vercel.com/ammar-ahmed-khans-projects-6b1515e7
78
+ ```
79
+
80
+ ### **Hugging Face:** ⏳ Rebuilding with latest backend
81
+ ```
82
+ https://huggingface.co/spaces/ammaraak/todo-app
83
+ ```
84
+
85
+ ---
86
+
87
+ ## πŸ§ͺ Test Steps (5 Minutes Baad)
88
+
89
+ ### **Mobile Test:**
90
+ 1. **Open your phone or browser mobile view**
91
+ 2. **Go to:** `https://frontend-48posvy29-ammar-ahmed-khans-projects-6b1515e7.vercel.app`
92
+ 3. **Login** with your credentials
93
+ 4. **Go to Dashboard**
94
+ 5. **Tap the AI chat button** (bottom-right corner)
95
+ 6. **Verify:**
96
+ - βœ… Chat panel opens with full width (with small margins)
97
+ - βœ… Panel height fits screen (not too tall)
98
+ - βœ… Input field and send button are touch-friendly
99
+ - βœ… Close button works
100
+ - βœ… Messages scroll properly
101
+
102
+ ### **Desktop Test:**
103
+ 1. **Open desktop browser**
104
+ 2. **Go to same URL**
105
+ 3. **Verify:**
106
+ - βœ… Chat panel is fixed width on right side
107
+ - βœ… Button is larger (64px)
108
+ - βœ… All functionality works
109
+
110
+ ---
111
+
112
+ ## πŸ“Š Before vs After
113
+
114
+ ### **BEFORE (Mobile):**
115
+ ```
116
+ ❌ Chat panel fixed width (not full screen)
117
+ ❌ Panel too tall for mobile viewport
118
+ ❌ Button too large (64px)
119
+ ❌ Input text too large
120
+ ❌ Touch targets too small
121
+ ```
122
+
123
+ ### **AFTER (Mobile):**
124
+ ```
125
+ βœ… Chat panel full width (with margins)
126
+ βœ… Panel height fits viewport (calc(100vh-80px))
127
+ βœ… Button properly sized (56px)
128
+ βœ… Text size appropriate for mobile (text-sm)
129
+ βœ… Touch targets large enough (40px minimum)
130
+ βœ… Proper positioning (bottom-20 to avoid overlap)
131
+ ```
132
+
133
+ ---
134
+
135
+ ## 🎯 Breakdown by Screen Size
136
+
137
+ ### **Mobile (< 768px):**
138
+ - Chat Panel: Full width with 16px margins on each side
139
+ - Chat Button: 56x56px at bottom-20
140
+ - Input Padding: 12px (p-3)
141
+ - Input Text: 14px (text-sm)
142
+ - Send Button: 40px min-width
143
+
144
+ ### **Desktop (>= 768px):**
145
+ - Chat Panel: Fixed width (max-w-md = 448px) on right side
146
+ - Chat Button: 64x64px at bottom-6
147
+ - Input Padding: 16px (p-4)
148
+ - Input Text: 16px (text-base)
149
+ - Send Button: 60px min-width
150
+
151
+ ---
152
+
153
+ ## πŸ”§ Technical Details
154
+
155
+ ### **Responsive Classes Used:**
156
+ - `left-4 right-4` - Mobile full width
157
+ - `md:left-auto md:right-6` - Desktop right side
158
+ - `h-[calc(100vh-80px)]` - Mobile viewport height
159
+ - `md:h-auto` - Desktop fixed height
160
+ - `w-14 h-14` - Mobile button size
161
+ - `md:w-16 md:h-16` - Desktop button size
162
+ - `text-sm md:text-base` - Responsive text
163
+ - `min-w-[40px] md:min-w-[60px]` - Touch targets
164
+
165
+ ### **Key Changes:**
166
+ 1. Used Tailwind's `md:` breakpoint for desktop-specific styles
167
+ 2. Calculated mobile height using `calc(100vh-80px)` to avoid overflow
168
+ 3. Made touch targets at least 40px for mobile usability
169
+ 4. Used smaller padding and text on mobile for space efficiency
170
+
171
+ ---
172
+
173
+ ## βœ… Summary
174
+
175
+ **Fixed Issues:**
176
+ 1. βœ… Chat panel now full width on mobile
177
+ 2. βœ… Chat button properly sized on mobile
178
+ 3. βœ… Input field mobile-friendly
179
+ 4. βœ… All touch targets appropriate size
180
+ 5. βœ… Height calculations prevent viewport overflow
181
+ 6. βœ… Pushed to GitHub for Vercel deployment
182
+
183
+ **Next:** Wait 5 minutes for Vercel deployment, then test on mobile!
184
+
185
+ ---
186
+
187
+ *Fixed: 2026-01-28*
188
+ *Branch: 001-ai-assistant*
189
+ *Deployment: Vercel auto-deploying*
190
+ *Result: Chat will be mobile responsive after deployment!*
PLAN-EMAIL-REMINDERS.md ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Email Reminder System for Todo App
2
+
3
+ ## Overview
4
+ Add email reminder functionality that sends notifications to users 1 day before their task's due date.
5
+
6
+ ## Requirements (from user)
7
+ - Send reminder to user's login email
8
+ - Trigger: 1 day before due date/time
9
+ - Only for tasks with due dates set
10
+ - Email service: Gmail SMTP
11
+ - Email content: Task title, description, due date/time
12
+
13
+ ---
14
+
15
+ ## Architecture
16
+
17
+ ### Components to Add
18
+
19
+ 1. **Database Schema Changes**
20
+ - Add `reminder_sent` boolean column to `todos` table
21
+ - Tracks whether reminder has been sent for each task
22
+
23
+ 2. **Email Service Module**
24
+ - New module: `backend/src/services/email_service.py`
25
+ - Handles SMTP connection and email sending
26
+ - Uses Gmail SMTP with app-specific password
27
+
28
+ 3. **Background Scheduler**
29
+ - Add APScheduler to FastAPI lifespan
30
+ - Runs every hour to check for tasks needing reminders
31
+ - Finds tasks due in ~24 hours that haven't had reminders sent
32
+
33
+ 4. **Frontend UI Updates**
34
+ - Add reminder indicator in CreateTodoModal
35
+ - Show reminder status in TodoList
36
+ - Display "Reminder will be sent 1 day before" message
37
+
38
+ ---
39
+
40
+ ## Implementation Plan
41
+
42
+ ### Phase 1: Database & Backend Core
43
+
44
+ #### 1.1 Update Todo Model
45
+ **File**: `backend/src/models/todo.py`
46
+
47
+ Add field:
48
+ ```python
49
+ reminder_sent: Optional[bool] = Field(
50
+ default=False,
51
+ description='Whether reminder email has been sent',
52
+ )
53
+ ```
54
+
55
+ #### 1.2 Create Database Migration
56
+ **Command**: From `backend/` directory
57
+ ```bash
58
+ alembic revision --autogenerate -m "add reminder_sent to todos"
59
+ ```
60
+
61
+ **Migration will add**:
62
+ ```sql
63
+ ALTER TABLE todos ADD COLUMN reminder_sent BOOLEAN DEFAULT FALSE;
64
+ ```
65
+
66
+ **Apply migration**:
67
+ ```bash
68
+ alembic upgrade head
69
+ ```
70
+
71
+ #### 1.3 Update Todo Schemas
72
+ **File**: `backend/src/schemas/todo.py`
73
+
74
+ Update `TodoCreateRequest` and `TodoResponse` to include `reminder_sent` field
75
+
76
+ ---
77
+
78
+ ### Phase 2: Email Service
79
+
80
+ #### 2.1 Create Email Service Module
81
+ **New File**: `backend/src/services/email_service.py`
82
+
83
+ Handles SMTP connection to Gmail and sends HTML email reminders.
84
+
85
+ #### 2.2 Update Configuration
86
+ **File**: `backend/src/core/config.py`
87
+
88
+ Add environment variables:
89
+ ```python
90
+ gmail_email: Optional[str] = Field(None, env="GMAIL_EMAIL")
91
+ gmail_app_password: Optional[str] = Field(None, env="GMAIL_APP_PASSWORD")
92
+ ```
93
+
94
+ #### 2.3 Update requirements.txt
95
+ **File**: `backend/requirements.txt`
96
+
97
+ ```
98
+ apscheduler>=3.10.0
99
+ ```
100
+
101
+ ---
102
+
103
+ ### Phase 3: Reminder Scheduler
104
+
105
+ #### 3.1 Create Reminder Service
106
+ **New File**: `backend/src/services/reminder_service.py`
107
+
108
+ Function `check_and_send_reminders(session)` that:
109
+ - Finds tasks due within 25 hours
110
+ - Filters out completed tasks
111
+ - Filters out tasks that already had reminders sent
112
+ - Sends email for each eligible task
113
+ - Marks task as `reminder_sent = True`
114
+
115
+ #### 3.2 Integrate Scheduler into FastAPI
116
+ **File**: `backend/src/main.py`
117
+
118
+ Update lifespan function to:
119
+ - Initialize APScheduler on startup
120
+ - Add job to run `check_and_send_reminders` every hour
121
+ - Shutdown scheduler on app shutdown
122
+ - Only start if Gmail credentials are configured
123
+
124
+ ---
125
+
126
+ ### Phase 4: Frontend Updates
127
+
128
+ #### 4.1 Update CreateTodoModal
129
+ **File**: `frontend/src/components/dashboard/CreateTodoModal.tsx`
130
+
131
+ Add info message after due date field showing:
132
+ "πŸ”” Reminder email will be sent 1 day before due date"
133
+
134
+ #### 4.2 Update TodoList (Optional)
135
+ **File**: `frontend/src/components/dashboard/TodoList.tsx`
136
+
137
+ Add reminder icon (πŸ””) next to tasks with due dates
138
+
139
+ ---
140
+
141
+ ### Phase 5: Environment Configuration
142
+
143
+ #### 5.1 Backend Environment Variables
144
+ **File**: `backend/.env`
145
+
146
+ ```bash
147
+ # Gmail SMTP Configuration
148
+ GMAIL_EMAIL=your-email@gmail.com
149
+ GMAIL_APP_PASSWORD=your-app-specific-password
150
+ ```
151
+
152
+ #### 5.2 Generate Gmail App Password
153
+ Instructions:
154
+ 1. Go to https://myaccount.google.com/security
155
+ 2. Enable 2-Step Verification
156
+ 3. Search for "App Passwords"
157
+ 4. Create app password: "Mail" + "Other (Custom name)" = "Todo App"
158
+ 5. Copy 16-character password
159
+ 6. Add to `.env`
160
+
161
+ ---
162
+
163
+ ## Implementation Files Summary
164
+
165
+ ### New Files to Create
166
+ 1. `backend/src/services/email_service.py` - Email sending logic
167
+ 2. `backend/src/services/reminder_service.py` - Reminder checking logic
168
+
169
+ ### Files to Modify
170
+ 1. `backend/src/models/todo.py` - Add `reminder_sent` field
171
+ 2. `backend/src/schemas/todo.py` - Update schemas
172
+ 3. `backend/src/core/config.py` - Add Gmail config
173
+ 4. `backend/src/main.py` - Add scheduler to lifespan
174
+ 5. `backend/requirements.txt` - Add APScheduler
175
+ 6. `frontend/src/components/dashboard/CreateTodoModal.tsx` - Show reminder info
176
+ 7. `frontend/src/components/dashboard/TodoList.tsx` - Add reminder indicator (optional)
177
+
178
+ ### Database
179
+ 1. Run: `alembic revision --autogenerate -m "add reminder_sent to todos"`
180
+ 2. Run: `alembic upgrade head`
181
+
182
+ ---
183
+
184
+ ## Testing Plan
185
+
186
+ ### Backend Testing
187
+ 1. Run migration to add `reminder_sent` column
188
+ 2. Test email service with sample email
189
+ 3. Create task due in ~24 hours
190
+ 4. Manually trigger reminder check
191
+ 5. Verify email received and `reminder_sent = True`
192
+
193
+ ### Frontend Testing
194
+ 1. Open Create Todo modal
195
+ 2. Set due date
196
+ 3. Verify reminder message appears
197
+ 4. Create task and verify indicator shows in list
198
+
199
+ ### End-to-End Testing
200
+ 1. Create task with due date = tomorrow
201
+ 2. Wait for hourly check (or trigger manually)
202
+ 3. Check email arrives
203
+ 4. Verify no duplicate emails for same task
204
+
205
+ ---
206
+
207
+ ## Deployment Checklist
208
+
209
+ ### Hugging Face Space Secrets
210
+ Add to your Hugging Face Space:
211
+ - `GMAIL_EMAIL`: Your Gmail address
212
+ - `GMAIL_APP_PASSWORD`: Gmail app-specific password
213
+
214
+ ### Migration on Production
215
+ ```bash
216
+ cd hf-space/backend
217
+ alembic upgrade head
218
+ ```
219
+
220
+ ### Verification
221
+ 1. Check logs for "βœ… Reminder scheduler started"
222
+ 2. Monitor `/health` endpoint
223
+ 3. Check error logs for email failures
224
+
225
+ ---
226
+
227
+ ## Security Considerations
228
+
229
+ 1. **Gmail App Password**: Never commit to git, always use env vars
230
+ 2. **Rate Limiting**: Hourly checks minimize Gmail API usage
231
+ 3. **Error Handling**: Email failures don't crash the app
232
+ 4. **User Privacy**: Only user's own email receives their reminders
233
+
234
+ ---
235
+
236
+ ## Future Enhancements (Optional)
237
+
238
+ 1. Customizable reminder timing (1 day, 1 hour, etc.)
239
+ 2. Snooze functionality
240
+ 3. Overdue task reminders
241
+ 4. Email preferences in user profile
242
+ 5. Multiple reminders per task
243
+ 6. Unsubscribe link in emails
244
+
245
+ ---
246
+
247
+ ## Rollback Plan
248
+
249
+ If issues arise:
250
+ 1. Disable scheduler: Remove Gmail env vars
251
+ 2. Revert migration: `alembic downgrade -1`
252
+ 3. Remove `reminder_sent` handling from code
backend/alembic/versions/002_add_reminder_sent.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """add reminder_sent to todos
2
+
3
+ Revision ID: 002
4
+ Revises: 29f774bde39a
5
+ Create Date: 2026-01-28 02:41:00.000000
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = '002'
16
+ down_revision: Union[str, None] = '29f774bde39a'
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ op.add_column('todos', sa.Column('reminder_sent', sa.Boolean(), nullable=True))
24
+ op.execute("UPDATE todos SET reminder_sent = FALSE")
25
+ op.alter_column('todos', 'reminder_sent', nullable=False)
26
+ # ### end Alembic commands ###
27
+
28
+
29
+ def downgrade() -> None:
30
+ # ### commands auto generated by Alembic - please adjust! ###
31
+ op.drop_column('todos', 'reminder_sent')
32
+ # ### end Alembic commands ###
backend/requirements.txt CHANGED
@@ -12,3 +12,4 @@ httpx>=0.26.0
12
  pydantic>=2.5.0
13
  pydantic-settings>=2.1.0
14
  python-dotenv>=1.0.0
 
 
12
  pydantic>=2.5.0
13
  pydantic-settings>=2.1.0
14
  python-dotenv>=1.0.0
15
+ apscheduler>=3.10.0
backend/src/api/todos.py CHANGED
@@ -72,6 +72,7 @@ async def list_todos(
72
  priority=todo.priority.value,
73
  tags=todo.tags,
74
  due_date=todo.due_date.isoformat() if todo.due_date else None,
 
75
  created_at=todo.created_at.isoformat(),
76
  updated_at=todo.updated_at.isoformat(),
77
  )
@@ -115,6 +116,7 @@ async def create_todo(
115
  priority=todo.priority.value,
116
  tags=todo.tags,
117
  due_date=todo.due_date.isoformat() if todo.due_date else None,
 
118
  created_at=todo.created_at.isoformat(),
119
  updated_at=todo.updated_at.isoformat(),
120
  )
@@ -167,6 +169,7 @@ async def toggle_todo_post(
167
  priority=todo.priority.value,
168
  tags=todo.tags,
169
  due_date=todo.due_date.isoformat() if todo.due_date else None,
 
170
  created_at=todo.created_at.isoformat(),
171
  updated_at=todo.updated_at.isoformat(),
172
  )
@@ -217,6 +220,7 @@ async def toggle_complete(
217
  priority=todo.priority.value,
218
  tags=todo.tags,
219
  due_date=todo.due_date.isoformat() if todo.due_date else None,
 
220
  created_at=todo.created_at.isoformat(),
221
  updated_at=todo.updated_at.isoformat(),
222
  )
@@ -255,6 +259,7 @@ async def get_todo(
255
  priority=todo.priority.value,
256
  tags=todo.tags,
257
  due_date=todo.due_date.isoformat() if todo.due_date else None,
 
258
  created_at=todo.created_at.isoformat(),
259
  updated_at=todo.updated_at.isoformat(),
260
  )
@@ -310,6 +315,7 @@ async def update_todo(
310
  priority=todo.priority.value,
311
  tags=todo.tags,
312
  due_date=todo.due_date.isoformat() if todo.due_date else None,
 
313
  created_at=todo.created_at.isoformat(),
314
  updated_at=todo.updated_at.isoformat(),
315
  )
 
72
  priority=todo.priority.value,
73
  tags=todo.tags,
74
  due_date=todo.due_date.isoformat() if todo.due_date else None,
75
+ reminder_sent=todo.reminder_sent,
76
  created_at=todo.created_at.isoformat(),
77
  updated_at=todo.updated_at.isoformat(),
78
  )
 
116
  priority=todo.priority.value,
117
  tags=todo.tags,
118
  due_date=todo.due_date.isoformat() if todo.due_date else None,
119
+ reminder_sent=todo.reminder_sent,
120
  created_at=todo.created_at.isoformat(),
121
  updated_at=todo.updated_at.isoformat(),
122
  )
 
169
  priority=todo.priority.value,
170
  tags=todo.tags,
171
  due_date=todo.due_date.isoformat() if todo.due_date else None,
172
+ reminder_sent=todo.reminder_sent,
173
  created_at=todo.created_at.isoformat(),
174
  updated_at=todo.updated_at.isoformat(),
175
  )
 
220
  priority=todo.priority.value,
221
  tags=todo.tags,
222
  due_date=todo.due_date.isoformat() if todo.due_date else None,
223
+ reminder_sent=todo.reminder_sent,
224
  created_at=todo.created_at.isoformat(),
225
  updated_at=todo.updated_at.isoformat(),
226
  )
 
259
  priority=todo.priority.value,
260
  tags=todo.tags,
261
  due_date=todo.due_date.isoformat() if todo.due_date else None,
262
+ reminder_sent=todo.reminder_sent,
263
  created_at=todo.created_at.isoformat(),
264
  updated_at=todo.updated_at.isoformat(),
265
  )
 
315
  priority=todo.priority.value,
316
  tags=todo.tags,
317
  due_date=todo.due_date.isoformat() if todo.due_date else None,
318
+ reminder_sent=todo.reminder_sent,
319
  created_at=todo.created_at.isoformat(),
320
  updated_at=todo.updated_at.isoformat(),
321
  )
backend/src/core/config.py CHANGED
@@ -58,6 +58,16 @@ class Settings(BaseSettings):
58
  default=None, description='Hugging Face API key'
59
  )
60
 
 
 
 
 
 
 
 
 
 
 
61
  # ========================================
62
  # Frontend URL
63
  # ========================================
 
58
  default=None, description='Hugging Face API key'
59
  )
60
 
61
+ # ========================================
62
+ # Email Configuration (Gmail SMTP)
63
+ # ========================================
64
+ gmail_email: Optional[str] = Field(
65
+ default=None, description='Gmail address for sending reminders'
66
+ )
67
+ gmail_app_password: Optional[str] = Field(
68
+ default=None, description='Gmail app-specific password for SMTP'
69
+ )
70
+
71
  # ========================================
72
  # Frontend URL
73
  # ========================================
backend/src/main.py CHANGED
@@ -21,6 +21,9 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
21
 
22
  Handles startup and shutdown events.
23
  """
 
 
 
24
  # Startup
25
  print(f"Starting Todo App API")
26
  print(f"Environment: {settings.env}")
@@ -32,9 +35,44 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
32
  init_db()
33
  print("Database initialized")
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  yield
36
 
37
  # Shutdown
 
 
 
38
  print("Shutting down Todo App API")
39
 
40
 
 
21
 
22
  Handles startup and shutdown events.
23
  """
24
+ # Scheduler instance
25
+ scheduler = None
26
+
27
  # Startup
28
  print(f"Starting Todo App API")
29
  print(f"Environment: {settings.env}")
 
35
  init_db()
36
  print("Database initialized")
37
 
38
+ # Start reminder scheduler if Gmail configured
39
+ if settings.gmail_email and settings.gmail_app_password:
40
+ try:
41
+ from apscheduler.schedulers.asyncio import AsyncIOScheduler
42
+ from src.services.reminder_service import check_and_send_reminders
43
+ from src.core.database import get_session
44
+
45
+ scheduler = AsyncIOScheduler()
46
+
47
+ # Add job to check reminders every hour
48
+ def run_reminder_check():
49
+ """Wrapper to get fresh session for each check."""
50
+ session = next(get_session())
51
+ try:
52
+ check_and_send_reminders(session)
53
+ finally:
54
+ session.close()
55
+
56
+ scheduler.add_job(
57
+ run_reminder_check,
58
+ 'interval',
59
+ hours=1,
60
+ id='reminder_checker',
61
+ name='Check and send task reminders'
62
+ )
63
+ scheduler.start()
64
+ print("βœ… Reminder scheduler started (runs every hour)")
65
+ except Exception as e:
66
+ print(f"⚠️ Failed to start reminder scheduler: {e}")
67
+ else:
68
+ print("⚠️ Reminder scheduler disabled (Gmail not configured)")
69
+
70
  yield
71
 
72
  # Shutdown
73
+ if scheduler:
74
+ scheduler.shutdown()
75
+ print("Reminder scheduler stopped")
76
  print("Shutting down Todo App API")
77
 
78
 
backend/src/models/todo.py CHANGED
@@ -76,6 +76,10 @@ class Todo(SQLModel, table=True):
76
  default=None,
77
  description='Completion timestamp',
78
  )
 
 
 
 
79
  user_id: UUID = Field(
80
  default=None,
81
  foreign_key='users.id',
 
76
  default=None,
77
  description='Completion timestamp',
78
  )
79
+ reminder_sent: Optional[bool] = Field(
80
+ default=False,
81
+ description='Whether reminder email has been sent',
82
+ )
83
  user_id: UUID = Field(
84
  default=None,
85
  foreign_key='users.id',
backend/src/schemas/todo.py CHANGED
@@ -38,6 +38,7 @@ class TodoResponse(BaseModel):
38
  priority: str
39
  tags: Optional[List[str]]
40
  due_date: Optional[str]
 
41
  created_at: str
42
  updated_at: str
43
 
 
38
  priority: str
39
  tags: Optional[List[str]]
40
  due_date: Optional[str]
41
+ reminder_sent: Optional[bool]
42
  created_at: str
43
  updated_at: str
44
 
backend/src/services/email_service.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Email service for sending reminders via Gmail SMTP.
3
+ """
4
+ import smtplib
5
+ from email.mime.text import MIMEText
6
+ from email.mime.multipart import MIMEMultipart
7
+ from typing import Optional
8
+ from datetime import datetime
9
+
10
+ from src.core.config import settings
11
+
12
+
13
+ class EmailService:
14
+ """Service for sending emails via Gmail SMTP."""
15
+
16
+ def __init__(self):
17
+ self.smtp_server = "smtp.gmail.com"
18
+ self.smtp_port = 587
19
+ self.gmail_email = getattr(settings, 'gmail_email', None)
20
+ self.gmail_app_password = getattr(settings, 'gmail_app_password', None)
21
+
22
+ def send_reminder(
23
+ self,
24
+ to_email: str,
25
+ task_title: str,
26
+ task_description: Optional[str],
27
+ due_date: datetime,
28
+ priority: Optional[str] = None,
29
+ tags: Optional[list] = None,
30
+ task_id: Optional[str] = None
31
+ ) -> bool:
32
+ """
33
+ Send reminder email for task due soon.
34
+
35
+ Args:
36
+ to_email: Recipient email address
37
+ task_title: Title of the task
38
+ task_description: Detailed description of the task
39
+ due_date: When the task is due
40
+ priority: Task priority (low, medium, high)
41
+ tags: Task tags
42
+ task_id: Unique task identifier
43
+
44
+ Returns:
45
+ bool: True if email sent successfully, False otherwise
46
+ """
47
+ if not self.gmail_email or not self.gmail_app_password:
48
+ print("Email service not configured: Gmail credentials missing")
49
+ return False
50
+
51
+ try:
52
+ # Create message
53
+ msg = MIMEMultipart('alternative')
54
+ msg['Subject'] = f'πŸ”” Reminder: Task Due Tomorrow - {task_title}'
55
+ msg['From'] = self.gmail_email
56
+ msg['To'] = to_email
57
+
58
+ # Format priority for display
59
+ priority_display = {
60
+ 'low': '🟒 Low',
61
+ 'medium': '🟑 Medium',
62
+ 'high': 'πŸ”΄ High'
63
+ }.get(priority or 'medium', 'βšͺ Medium')
64
+
65
+ # Format tags for display
66
+ tags_display = ''
67
+ if tags:
68
+ tags_display = '<p style="color: #666;"><strong>🏷️ Tags:</strong> ' + ', '.join(tags) + '</p>'
69
+
70
+ # Calculate time until due
71
+ now = datetime.utcnow()
72
+ time_until_due = due_date - now
73
+ hours_remaining = int(time_until_due.total_seconds() / 3600)
74
+
75
+ # Email body with full task details
76
+ html = f"""
77
+ <html>
78
+ <body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; color: #333;">
79
+ <div style="max-width: 600px; margin: 0 auto; padding: 20px;">
80
+ <h2 style="color: #667eea; margin-bottom: 10px;">πŸ”” Task Reminder</h2>
81
+ <p style="color: #666; font-size: 16px;">Your task is due <strong>tomorrow</strong>:</p>
82
+
83
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; border-radius: 12px; margin: 25px 0; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
84
+ <h3 style="color: #ffffff; margin-top: 0; font-size: 24px;">{task_title}</h3>
85
+
86
+ {f'<p style="color: #e0e7ff; font-size: 16px; margin: 15px 0; line-height: 1.8;">{task_description}</p>' if task_description else ''}
87
+
88
+ <div style="background: rgba(255,255,255,0.2); padding: 20px; border-radius: 8px; margin-top: 20px;">
89
+ <p style="color: #ffffff; margin: 8px 0; font-size: 15px;">
90
+ <strong>πŸ“… Due Date:</strong> {due_date.strftime('%B %d, %Y at %I:%M %p')}
91
+ </p>
92
+ <p style="color: #ffffff; margin: 8px 0; font-size: 15px;">
93
+ <strong>⏰ Time Remaining:</strong> {hours_remaining} hours
94
+ </p>
95
+ <p style="color: #ffffff; margin: 8px 0; font-size: 15px;">
96
+ <strong>🎯 Priority:</strong> {priority_display}
97
+ </p>
98
+ {f'{tags_display}' if tags_display else ''}
99
+ </div>
100
+ </div>
101
+
102
+ <div style="background: #f8f9fa; padding: 20px; border-radius: 8px; border-left: 4px solid #667eea; margin: 20px 0;">
103
+ <p style="margin: 0; color: #495057; font-size: 15px;">
104
+ <strong>πŸ’‘ Tip:</strong> Make sure to complete your task on time to stay productive!
105
+ </p>
106
+ </div>
107
+
108
+ <hr style="margin: 30px 0; border: none; border-top: 1px solid #dee2e6;">
109
+ <p style="color: #6c757d; font-size: 12px; text-align: center;">
110
+ This is an automated reminder from your <strong>Todo App</strong>.<br>
111
+ You received this because you created this task with a due date.<br>
112
+ Task ID: {task_id or 'N/A'}
113
+ </p>
114
+ </div>
115
+ </body>
116
+ </html>
117
+ """
118
+
119
+ part = MIMEText(html, 'html')
120
+ msg.attach(part)
121
+
122
+ # Send email
123
+ server = smtplib.SMTP(self.smtp_server, self.smtp_port)
124
+ server.starttls()
125
+ server.login(self.gmail_email, self.gmail_app_password)
126
+ server.send_message(msg)
127
+ server.quit()
128
+
129
+ print(f"βœ… Reminder email sent to {to_email} for task: {task_title}")
130
+ return True
131
+ except Exception as e:
132
+ print(f"❌ Failed to send email: {e}")
133
+ return False
134
+
135
+
136
+ # Singleton instance
137
+ email_service = EmailService()
backend/src/services/reminder_service.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Background service to check and send task reminders.
3
+ """
4
+ from datetime import datetime, timedelta
5
+ from typing import List
6
+ from uuid import UUID
7
+
8
+ from sqlalchemy.orm import Session
9
+ from sqlalchemy import and_
10
+
11
+ from src.models.todo import Todo, Status
12
+ from src.models.user import User
13
+ from src.services.email_service import email_service
14
+
15
+
16
+ def check_and_send_reminders(session: Session) -> dict:
17
+ """
18
+ Check for tasks due in ~24 hours and send reminders.
19
+
20
+ This function should be called hourly by the scheduler.
21
+ It finds tasks that are:
22
+ - Not completed
23
+ - Have a due_date set
24
+ - Due within the next 25 hours
25
+ - Haven't had a reminder sent yet
26
+
27
+ Args:
28
+ session: Database session
29
+
30
+ Returns:
31
+ dict with count of reminders sent and errors
32
+ """
33
+ # Calculate time window: now to now+25 hours
34
+ # This ensures we catch tasks due "tomorrow" when checking hourly
35
+ now = datetime.utcnow()
36
+ reminder_window_start = now
37
+ reminder_window_end = now + timedelta(hours=25)
38
+
39
+ print(f"πŸ” Checking for tasks due between {reminder_window_start} and {reminder_window_end}")
40
+
41
+ # Build query to find eligible tasks
42
+ query = session.query(Todo, User).join(
43
+ User, Todo.user_id == User.id
44
+ ).filter(
45
+ and_(
46
+ Todo.status != Status.COMPLETED, # Not completed
47
+ Todo.due_date.isnot(None), # Has due date
48
+ Todo.due_date >= reminder_window_start, # Due in future
49
+ Todo.due_date <= reminder_window_end, # Due within 25 hours
50
+ Todo.reminder_sent == False # Reminder not sent yet
51
+ )
52
+ )
53
+
54
+ results = query.all()
55
+ sent_count = 0
56
+ error_count = 0
57
+ skipped_count = 0
58
+
59
+ print(f"πŸ“‹ Found {len(results)} tasks eligible for reminders")
60
+
61
+ for todo, user in results:
62
+ try:
63
+ # Send reminder email with full task details
64
+ success = email_service.send_reminder(
65
+ to_email=user.email,
66
+ task_title=todo.title,
67
+ task_description=todo.description,
68
+ due_date=todo.due_date,
69
+ priority=todo.priority.value if todo.priority else None,
70
+ tags=todo.tags,
71
+ task_id=str(todo.id)
72
+ )
73
+
74
+ if success:
75
+ # Mark reminder as sent
76
+ todo.reminder_sent = True
77
+ session.commit()
78
+ sent_count += 1
79
+ print(f"βœ… Reminder sent for task '{todo.title}' to {user.email}")
80
+ else:
81
+ error_count += 1
82
+ print(f"❌ Failed to send reminder for task '{todo.title}'")
83
+
84
+ except Exception as e:
85
+ print(f"❌ Error sending reminder for task {todo.id}: {e}")
86
+ error_count += 1
87
+ session.rollback()
88
+
89
+ summary = {
90
+ "checked": len(results),
91
+ "sent": sent_count,
92
+ "errors": error_count,
93
+ "skipped": skipped_count
94
+ }
95
+
96
+ print(f"πŸ“Š Reminder check complete: {summary}")
97
+ return summary
98
+
99
+
100
+ def send_test_reminder(session: Session, user_id: UUID) -> bool:
101
+ """
102
+ Send a test reminder to verify email configuration.
103
+
104
+ Args:
105
+ session: Database session
106
+ user_id: User ID to send test email to
107
+
108
+ Returns:
109
+ bool: True if test email sent successfully
110
+ """
111
+ from src.models.user import User
112
+
113
+ user = session.query(User).filter(User.id == user_id).first()
114
+ if not user:
115
+ print(f"User not found: {user_id}")
116
+ return False
117
+
118
+ test_due_date = datetime.utcnow() + timedelta(hours=24)
119
+
120
+ return email_service.send_reminder(
121
+ to_email=user.email,
122
+ task_title="πŸ§ͺ Test Task - Todo App Reminder",
123
+ task_description="This is a test reminder email to verify your email configuration is working correctly!",
124
+ due_date=test_due_date,
125
+ priority="medium",
126
+ tags=["test"],
127
+ task_id="test-task-id"
128
+ )
frontend/src/components/dashboard/CreateTodoModal.tsx CHANGED
@@ -187,6 +187,16 @@ export function CreateTodoModal({ onClose, onCreate }: CreateTodoModalProps) {
187
  onChange={(e) => setDueDate(e.target.value)}
188
  className="w-full px-2 sm:px-3 py-2 text-xs border border-gray-300 dark:border-green-500/50 rounded-lg focus:ring-2 focus:ring-blue-500 dark:focus:ring-green-500 dark:focus:shadow-[0_0_10px_rgba(74,222,128,0.5)] bg-white dark:bg-gray-800 text-gray-900 dark:text-white transition-all"
189
  />
 
 
 
 
 
 
 
 
 
 
190
  </div>
191
 
192
  <div>
 
187
  onChange={(e) => setDueDate(e.target.value)}
188
  className="w-full px-2 sm:px-3 py-2 text-xs border border-gray-300 dark:border-green-500/50 rounded-lg focus:ring-2 focus:ring-blue-500 dark:focus:ring-green-500 dark:focus:shadow-[0_0_10px_rgba(74,222,128,0.5)] bg-white dark:bg-gray-800 text-gray-900 dark:text-white transition-all"
189
  />
190
+ {dueDate && (
191
+ <div className="mt-1.5 p-2 bg-blue-50 dark:bg-blue-900/20 rounded-md border border-blue-200 dark:border-blue-800">
192
+ <div className="flex items-center gap-1.5 text-xs text-blue-700 dark:text-blue-300">
193
+ <svg className="w-3.5 h-3.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
194
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
195
+ </svg>
196
+ <span className="font-medium">Reminder email will be sent 1 day before</span>
197
+ </div>
198
+ </div>
199
+ )}
200
  </div>
201
 
202
  <div>
frontend/src/components/dashboard/TodoList.tsx CHANGED
@@ -108,6 +108,14 @@ export function TodoList({ todos, loading, onToggleComplete, onDelete }: TodoLis
108
  <span className="flex items-center gap-1 text-xs text-gray-500 dark:text-gray-400">
109
  <Calendar className="w-3 h-3" />
110
  {format(new Date(todo.due_date), 'MMM d, yyyy')}
 
 
 
 
 
 
 
 
111
  </span>
112
  )}
113
 
 
108
  <span className="flex items-center gap-1 text-xs text-gray-500 dark:text-gray-400">
109
  <Calendar className="w-3 h-3" />
110
  {format(new Date(todo.due_date), 'MMM d, yyyy')}
111
+ {!todo.reminder_sent && todo.status !== 'completed' && (
112
+ <span
113
+ className="ml-1 text-blue-500 dark:text-blue-400"
114
+ title="Reminder scheduled"
115
+ >
116
+ πŸ””
117
+ </span>
118
+ )}
119
  </span>
120
  )}
121
 
frontend/src/types/index.ts CHANGED
@@ -71,6 +71,7 @@ export interface Todo {
71
  completed_at?: string;
72
  user_id: string;
73
  tags?: string[];
 
74
  created_at: string;
75
  updated_at: string;
76
  }
 
71
  completed_at?: string;
72
  user_id: string;
73
  tags?: string[];
74
+ reminder_sent?: boolean;
75
  created_at: string;
76
  updated_at: string;
77
  }