66from app import db
77from apscheduler .schedulers .background import BackgroundScheduler
88import atexit
9+ import threading
10+ import time
911
1012def get_currency_symbol (currency_code ):
1113 """Get currency symbol for display"""
@@ -188,15 +190,17 @@ def send_expiry_notification(app, user, subscriptions):
188190 # Log connection attempt
189191 print (f"🔌 Connecting to { app .config ['MAIL_SERVER' ]} :{ app .config ['MAIL_PORT' ]} " )
190192
191- # Use SSL for port 465, TLS for other ports
193+ # Use SSL for port 465, TLS for other ports with timeout
194+ smtp_timeout = 10 # 10 second timeout for SMTP operations
195+
192196 if app .config ['MAIL_PORT' ] == 465 :
193197 # Port 465 uses implicit SSL
194198 print ("🔒 Using SSL connection (port 465)" )
195- server = smtplib .SMTP_SSL (app .config ['MAIL_SERVER' ], app .config ['MAIL_PORT' ])
199+ server = smtplib .SMTP_SSL (app .config ['MAIL_SERVER' ], app .config ['MAIL_PORT' ], timeout = smtp_timeout )
196200 else :
197201 # Other ports use explicit TLS or plain connection
198202 print (f"🔐 Using { 'TLS' if app .config ['MAIL_USE_TLS' ] else 'plain' } connection" )
199- server = smtplib .SMTP (app .config ['MAIL_SERVER' ], app .config ['MAIL_PORT' ])
203+ server = smtplib .SMTP (app .config ['MAIL_SERVER' ], app .config ['MAIL_PORT' ], timeout = smtp_timeout )
200204 if app .config ['MAIL_USE_TLS' ]:
201205 server .starttls ()
202206
@@ -217,13 +221,45 @@ def send_expiry_notification(app, user, subscriptions):
217221 print (f"❌ SMTP Connection failed for { user .username } : { e } " )
218222 print ("🔍 Check MAIL_SERVER and MAIL_PORT" )
219223 return False
224+ except smtplib .SMTPServerDisconnected as e :
225+ print (f"❌ SMTP Server disconnected for { user .username } : { e } " )
226+ print ("🔍 Mail server may be overloaded or timing out" )
227+ return False
220228 except smtplib .SMTPException as e :
221229 print (f"❌ SMTP error for { user .username } : { e } " )
222230 return False
231+ except ConnectionError as e :
232+ print (f"❌ Network connection error for { user .username } : { e } " )
233+ print ("🔍 Check network connectivity to mail server" )
234+ return False
235+ except TimeoutError as e :
236+ print (f"❌ Timeout error for { user .username } : { e } " )
237+ print ("🔍 Mail server is taking too long to respond" )
238+ return False
223239 except Exception as e :
224240 print (f"❌ Failed to send email to { user .username } : { e } " )
225241 return False
226242
243+ def check_expiring_subscriptions_with_timeout (app ):
244+ """Wrapper function to check expiring subscriptions with timeout protection"""
245+ def run_check ():
246+ try :
247+ check_expiring_subscriptions (app )
248+ except Exception as e :
249+ print (f"❌ Error in notification check: { e } " )
250+
251+ # Run the check in a separate thread with timeout
252+ thread = threading .Thread (target = run_check )
253+ thread .daemon = True
254+ thread .start ()
255+
256+ # Wait for completion with timeout
257+ thread .join (timeout = 60 ) # 60 second timeout for entire email check process
258+
259+ if thread .is_alive ():
260+ print ("⚠️ Email notification check timed out after 60 seconds" )
261+ # Thread will continue in background but won't block the scheduler
262+
227263def check_expiring_subscriptions (app ):
228264 """Check for expiring subscriptions and send notifications"""
229265 with app .app_context ():
@@ -310,11 +346,12 @@ def start_scheduler(app):
310346
311347 # Check every hour to respect user-specific notification times
312348 scheduler .add_job (
313- func = lambda : check_expiring_subscriptions (app ),
349+ func = lambda : check_expiring_subscriptions_with_timeout (app ),
314350 trigger = "interval" ,
315351 hours = 1 ,
316352 id = 'check_subscriptions' ,
317- replace_existing = True
353+ replace_existing = True ,
354+ max_instances = 1 # Prevent overlapping runs
318355 )
319356
320357 scheduler .start ()
@@ -422,15 +459,17 @@ def send_test_email(app, user):
422459 # Log connection attempt
423460 print (f"🔌 Connecting to { app .config ['MAIL_SERVER' ]} :{ app .config ['MAIL_PORT' ]} " )
424461
425- # Use SSL for port 465, TLS for other ports
462+ # Use SSL for port 465, TLS for other ports with timeout
463+ smtp_timeout = 10 # 10 second timeout for SMTP operations
464+
426465 if app .config ['MAIL_PORT' ] == 465 :
427466 # Port 465 uses implicit SSL
428467 print ("🔒 Using SSL connection (port 465)" )
429- server = smtplib .SMTP_SSL (app .config ['MAIL_SERVER' ], app .config ['MAIL_PORT' ])
468+ server = smtplib .SMTP_SSL (app .config ['MAIL_SERVER' ], app .config ['MAIL_PORT' ], timeout = smtp_timeout )
430469 else :
431470 # Other ports use explicit TLS or plain connection
432471 print (f"🔐 Using { 'TLS' if app .config ['MAIL_USE_TLS' ] else 'plain' } connection" )
433- server = smtplib .SMTP (app .config ['MAIL_SERVER' ], app .config ['MAIL_PORT' ])
472+ server = smtplib .SMTP (app .config ['MAIL_SERVER' ], app .config ['MAIL_PORT' ], timeout = smtp_timeout )
434473 if app .config ['MAIL_USE_TLS' ]:
435474 server .starttls ()
436475
@@ -460,13 +499,34 @@ def send_test_email(app, user):
460499 'success' : False ,
461500 'message' : error_msg
462501 }
502+ except smtplib .SMTPServerDisconnected as e :
503+ error_msg = f"Mail server disconnected: { e } . Server may be overloaded."
504+ print (f"❌ { error_msg } " )
505+ return {
506+ 'success' : False ,
507+ 'message' : error_msg
508+ }
463509 except smtplib .SMTPRecipientsRefused as e :
464510 error_msg = f"Recipient refused: { e } . Check email address."
465511 print (f"❌ { error_msg } " )
466512 return {
467513 'success' : False ,
468514 'message' : error_msg
469515 }
516+ except ConnectionError as e :
517+ error_msg = f"Network connection error: { e } . Check network connectivity."
518+ print (f"❌ { error_msg } " )
519+ return {
520+ 'success' : False ,
521+ 'message' : error_msg
522+ }
523+ except TimeoutError as e :
524+ error_msg = f"Connection timeout: { e } . Mail server is taking too long to respond."
525+ print (f"❌ { error_msg } " )
526+ return {
527+ 'success' : False ,
528+ 'message' : error_msg
529+ }
470530 except Exception as e :
471531 error_msg = f"Unexpected error: { e } "
472532 print (f"❌ { error_msg } " )
0 commit comments