rairo commited on
Commit
5e18bc5
·
verified ·
1 Parent(s): 1c10adc

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +102 -1
main.py CHANGED
@@ -21,6 +21,9 @@ from sozo_gen import (
21
  deepgram_tts
22
  )
23
  import logging
 
 
 
24
  # -----------------------------------------------------------------------------
25
  # 1. CONFIGURATION & INITIALIZATION
26
  # -----------------------------------------------------------------------------
@@ -69,11 +72,37 @@ def is_valid_email(email):
69
  regex = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
70
  return re.match(regex, email) is not None
71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  def _send_notification(user_id, user_email, message_content, send_email=False, email_subject=None, email_body=None):
73
  """
74
  Internal helper to send notifications.
75
  Creates an in-app notification in Firebase and optionally sends an email via Resend.
76
  If user_id is None, it will only attempt to send an email.
 
77
  """
78
  timestamp = datetime.now(timezone.utc).isoformat()
79
 
@@ -100,6 +129,9 @@ def _send_notification(user_id, user_email, message_content, send_email=False, e
100
  logger.error("RESEND_API_KEY is not configured. Cannot send email.")
101
  return False
102
 
 
 
 
103
  # Clean the API key (remove any whitespace)
104
  api_key = RESEND_API_KEY.strip()
105
 
@@ -133,7 +165,20 @@ def _send_notification(user_id, user_email, message_content, send_email=False, e
133
  logger.debug(f"Response headers: {dict(response.headers)}")
134
  logger.debug(f"Response content: {response.text}")
135
 
136
- if response.status_code == 401:
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  logger.error("=== RESEND 401 UNAUTHORIZED DEBUG ===")
138
  logger.error(f"API Key starts correctly: {api_key.startswith('re_')}")
139
  logger.error(f"API Key length: {len(api_key)} (should be ~40 chars)")
@@ -161,6 +206,62 @@ def _send_notification(user_id, user_email, message_content, send_email=False, e
161
  return False
162
 
163
  return True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  # -----------------------------------------------------------------------------
165
  # 3. AUTHENTICATION & USER MANAGEMENT
166
  # -----------------------------------------------------------------------------
 
21
  deepgram_tts
22
  )
23
  import logging
24
+ import time
25
+ import threading
26
+
27
  # -----------------------------------------------------------------------------
28
  # 1. CONFIGURATION & INITIALIZATION
29
  # -----------------------------------------------------------------------------
 
72
  regex = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
73
  return re.match(regex, email) is not None
74
 
75
+
76
+
77
+ # Global rate limiter for Resend API
78
+ class ResendRateLimiter:
79
+ def __init__(self, requests_per_second=1):
80
+ self.requests_per_second = requests_per_second
81
+ self.min_interval = 1.0 / requests_per_second
82
+ self.last_request_time = 0
83
+ self.lock = threading.Lock()
84
+
85
+ def wait_if_needed(self):
86
+ with self.lock:
87
+ current_time = time.time()
88
+ time_since_last_request = current_time - self.last_request_time
89
+
90
+ if time_since_last_request < self.min_interval:
91
+ sleep_time = self.min_interval - time_since_last_request
92
+ logger.info(f"Rate limiting: waiting {sleep_time:.2f} seconds before sending email")
93
+ time.sleep(sleep_time)
94
+
95
+ self.last_request_time = time.time()
96
+
97
+ # Global instance - initialize once
98
+ resend_rate_limiter = ResendRateLimiter(requests_per_second=1)
99
+
100
  def _send_notification(user_id, user_email, message_content, send_email=False, email_subject=None, email_body=None):
101
  """
102
  Internal helper to send notifications.
103
  Creates an in-app notification in Firebase and optionally sends an email via Resend.
104
  If user_id is None, it will only attempt to send an email.
105
+ Rate limited to 1 email per second to respect Resend API limits.
106
  """
107
  timestamp = datetime.now(timezone.utc).isoformat()
108
 
 
129
  logger.error("RESEND_API_KEY is not configured. Cannot send email.")
130
  return False
131
 
132
+ # Apply rate limiting before making the request
133
+ resend_rate_limiter.wait_if_needed()
134
+
135
  # Clean the API key (remove any whitespace)
136
  api_key = RESEND_API_KEY.strip()
137
 
 
165
  logger.debug(f"Response headers: {dict(response.headers)}")
166
  logger.debug(f"Response content: {response.text}")
167
 
168
+ # Handle rate limiting response
169
+ if response.status_code == 429:
170
+ logger.warning(f"Rate limit hit despite internal limiting. Response: {response.text}")
171
+ # Extract retry-after if available
172
+ retry_after = response.headers.get('retry-after')
173
+ if retry_after:
174
+ logger.info(f"Server requested retry after {retry_after} seconds")
175
+ time.sleep(float(retry_after))
176
+ # Retry once
177
+ response = requests.post("https://api.resend.com/emails", headers=headers, json=payload)
178
+ response.raise_for_status()
179
+ else:
180
+ return False
181
+ elif response.status_code == 401:
182
  logger.error("=== RESEND 401 UNAUTHORIZED DEBUG ===")
183
  logger.error(f"API Key starts correctly: {api_key.startswith('re_')}")
184
  logger.error(f"API Key length: {len(api_key)} (should be ~40 chars)")
 
206
  return False
207
 
208
  return True
209
+
210
+
211
+ # Alternative: Batch email sending function for multiple emails
212
+ def send_batch_notifications(notifications_list):
213
+ """
214
+ Send multiple notifications with proper rate limiting.
215
+
216
+ Args:
217
+ notifications_list: List of dicts with keys:
218
+ - user_id (optional)
219
+ - user_email
220
+ - message_content
221
+ - send_email (bool)
222
+ - email_subject (optional)
223
+ - email_body (optional)
224
+
225
+ Returns:
226
+ dict: Results with success/failure counts
227
+ """
228
+ results = {
229
+ 'total': len(notifications_list),
230
+ 'successful': 0,
231
+ 'failed': 0,
232
+ 'errors': []
233
+ }
234
+
235
+ logger.info(f"Starting batch notification send for {results['total']} notifications")
236
+ start_time = time.time()
237
+
238
+ for i, notification in enumerate(notifications_list):
239
+ try:
240
+ success = _send_notification(
241
+ user_id=notification.get('user_id'),
242
+ user_email=notification.get('user_email'),
243
+ message_content=notification.get('message_content'),
244
+ send_email=notification.get('send_email', False),
245
+ email_subject=notification.get('email_subject'),
246
+ email_body=notification.get('email_body')
247
+ )
248
+
249
+ if success:
250
+ results['successful'] += 1
251
+ else:
252
+ results['failed'] += 1
253
+ results['errors'].append(f"Notification {i+1} failed")
254
+
255
+ except Exception as e:
256
+ results['failed'] += 1
257
+ results['errors'].append(f"Notification {i+1} error: {str(e)}")
258
+ logger.error(f"Unexpected error processing notification {i+1}: {e}")
259
+
260
+ elapsed_time = time.time() - start_time
261
+ logger.info(f"Batch notification completed in {elapsed_time:.2f} seconds. "
262
+ f"Success: {results['successful']}, Failed: {results['failed']}")
263
+
264
+ return results
265
  # -----------------------------------------------------------------------------
266
  # 3. AUTHENTICATION & USER MANAGEMENT
267
  # -----------------------------------------------------------------------------