saifisvibin commited on
Commit
b55c72c
·
1 Parent(s): 9dfb9fd

Add email verification and failed email detection with detailed error display

Browse files
backend/routers/email.py CHANGED
@@ -41,10 +41,22 @@ async def get_email_config():
41
 
42
  @router.get("/progress/{job_id}")
43
  async def get_email_progress(job_id: str):
44
- """Return the current progress of an email sending job"""
45
  if job_id not in email_progress:
46
  raise HTTPException(status_code=404, detail="Job not found")
47
- return email_progress[job_id]
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
  def send_email_task(
50
  job_id,
@@ -134,21 +146,55 @@ def send_email_task(
134
  print(f"Sending email to {email} via SendGrid...")
135
  sg = SendGridAPIClient(api_key)
136
  response = sg.send(message)
137
- print(f"Sent to {email}: Status {response.status_code}")
 
138
 
139
- # Update progress
140
- if job_id in email_progress:
141
- email_progress[job_id]["sent"] += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
  except Exception as e:
144
- print(f"Failed to send to {email}: {e}")
 
145
  import traceback
146
  traceback.print_exc()
147
 
148
- # Update progress even on failure
149
  if job_id in email_progress:
150
  email_progress[job_id]["sent"] += 1
151
  email_progress[job_id]["failed"] += 1
 
 
 
 
 
 
 
 
 
152
 
153
  @router.post("/send")
154
  async def send_emails(
 
41
 
42
  @router.get("/progress/{job_id}")
43
  async def get_email_progress(job_id: str):
44
+ """Return the current progress of an email sending job with detailed failure info"""
45
  if job_id not in email_progress:
46
  raise HTTPException(status_code=404, detail="Job not found")
47
+
48
+ progress = email_progress[job_id]
49
+
50
+ # Return detailed progress including failed email details
51
+ return {
52
+ "total": progress["total"],
53
+ "sent": progress["sent"],
54
+ "failed": progress["failed"],
55
+ "status": progress["status"],
56
+ "failed_emails": progress.get("failed_emails", []), # List of {email, name, error}
57
+ "success_emails": progress.get("success_emails", []) # List of emails sent successfully
58
+ }
59
+
60
 
61
  def send_email_task(
62
  job_id,
 
146
  print(f"Sending email to {email} via SendGrid...")
147
  sg = SendGridAPIClient(api_key)
148
  response = sg.send(message)
149
+ status_code = response.status_code
150
+ print(f"Sent to {email}: Status {status_code}")
151
 
152
+ # Check if email was actually sent successfully (2xx status codes)
153
+ if status_code >= 200 and status_code < 300:
154
+ # Update progress - successful
155
+ if job_id in email_progress:
156
+ email_progress[job_id]["sent"] += 1
157
+ # Track successful email
158
+ if "success_emails" not in email_progress[job_id]:
159
+ email_progress[job_id]["success_emails"] = []
160
+ email_progress[job_id]["success_emails"].append({
161
+ "email": email,
162
+ "name": name,
163
+ "status_code": status_code
164
+ })
165
+ else:
166
+ # Non-2xx status code - treat as failure
167
+ print(f"SendGrid returned non-success status {status_code} for {email}")
168
+ if job_id in email_progress:
169
+ email_progress[job_id]["sent"] += 1
170
+ email_progress[job_id]["failed"] += 1
171
+ if "failed_emails" not in email_progress[job_id]:
172
+ email_progress[job_id]["failed_emails"] = []
173
+ email_progress[job_id]["failed_emails"].append({
174
+ "email": email,
175
+ "name": name,
176
+ "error": f"SendGrid status code: {status_code}"
177
+ })
178
 
179
  except Exception as e:
180
+ error_message = str(e)
181
+ print(f"Failed to send to {email}: {error_message}")
182
  import traceback
183
  traceback.print_exc()
184
 
185
+ # Update progress with detailed failure info
186
  if job_id in email_progress:
187
  email_progress[job_id]["sent"] += 1
188
  email_progress[job_id]["failed"] += 1
189
+ # Track failed email with error details
190
+ if "failed_emails" not in email_progress[job_id]:
191
+ email_progress[job_id]["failed_emails"] = []
192
+ email_progress[job_id]["failed_emails"].append({
193
+ "email": email,
194
+ "name": name,
195
+ "error": error_message
196
+ })
197
+
198
 
199
  @router.post("/send")
200
  async def send_emails(
frontend/src/components/ReviewSendStep.jsx CHANGED
@@ -14,6 +14,7 @@ const ReviewSendStep = ({ fileData, designParams, mappings, onBack }) => {
14
  const [status, setStatus] = useState(null);
15
  const [progress, setProgress] = useState(null);
16
  const [jobId, setJobId] = useState(null);
 
17
 
18
  // Fetch email configuration on mount
19
  useEffect(() => {
@@ -67,7 +68,17 @@ const ReviewSendStep = ({ fileData, designParams, mappings, onBack }) => {
67
  if (progressData.sent >= progressData.total) {
68
  clearInterval(pollInterval);
69
  setSending(false);
70
- setStatus({ type: 'success', msg: `Successfully sent ${progressData.sent - progressData.failed} emails! ${progressData.failed > 0 ? `(${progressData.failed} failed)` : ''}` });
 
 
 
 
 
 
 
 
 
 
71
  setProgress(null);
72
  }
73
  } catch (pollError) {
@@ -291,12 +302,40 @@ const ReviewSendStep = ({ fileData, designParams, mappings, onBack }) => {
291
  animate={{ opacity: 1, y: 0 }}
292
  className={`p-4 rounded-lg border text-center ${status.type === 'success'
293
  ? 'bg-green-50 border-green-200 text-green-700'
294
- : 'bg-red-50 border-red-200 text-red-700'
 
 
295
  }`}
296
  >
297
  {status.msg}
298
  </motion.div>
299
  )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
  </div>
301
  </div>
302
 
 
14
  const [status, setStatus] = useState(null);
15
  const [progress, setProgress] = useState(null);
16
  const [jobId, setJobId] = useState(null);
17
+ const [failedEmails, setFailedEmails] = useState([]); // Store failed email details
18
 
19
  // Fetch email configuration on mount
20
  useEffect(() => {
 
68
  if (progressData.sent >= progressData.total) {
69
  clearInterval(pollInterval);
70
  setSending(false);
71
+
72
+ // Store failed emails for display
73
+ if (progressData.failed_emails && progressData.failed_emails.length > 0) {
74
+ setFailedEmails(progressData.failed_emails);
75
+ }
76
+
77
+ const successCount = progressData.sent - progressData.failed;
78
+ setStatus({
79
+ type: progressData.failed > 0 ? 'warning' : 'success',
80
+ msg: `Successfully sent ${successCount} emails!${progressData.failed > 0 ? ` (${progressData.failed} failed)` : ''}`
81
+ });
82
  setProgress(null);
83
  }
84
  } catch (pollError) {
 
302
  animate={{ opacity: 1, y: 0 }}
303
  className={`p-4 rounded-lg border text-center ${status.type === 'success'
304
  ? 'bg-green-50 border-green-200 text-green-700'
305
+ : status.type === 'warning'
306
+ ? 'bg-yellow-50 border-yellow-200 text-yellow-700'
307
+ : 'bg-red-50 border-red-200 text-red-700'
308
  }`}
309
  >
310
  {status.msg}
311
  </motion.div>
312
  )}
313
+
314
+ {/* Failed Emails Details */}
315
+ {failedEmails.length > 0 && (
316
+ <motion.div
317
+ initial={{ opacity: 0, y: 10 }}
318
+ animate={{ opacity: 1, y: 0 }}
319
+ className="bg-red-50 border border-red-200 p-6 rounded-lg"
320
+ >
321
+ <h4 className="text-sm font-semibold text-red-800 mb-3 flex items-center">
322
+ ⚠️ Failed Emails ({failedEmails.length})
323
+ </h4>
324
+ <div className="max-h-48 overflow-y-auto space-y-2">
325
+ {failedEmails.map((failed, index) => (
326
+ <div key={index} className="bg-white rounded p-3 border border-red-100">
327
+ <div className="flex justify-between items-start">
328
+ <div>
329
+ <p className="text-sm font-medium text-gray-900">{failed.name}</p>
330
+ <p className="text-xs text-gray-600">{failed.email}</p>
331
+ </div>
332
+ </div>
333
+ <p className="text-xs text-red-600 mt-1 font-mono">{failed.error}</p>
334
+ </div>
335
+ ))}
336
+ </div>
337
+ </motion.div>
338
+ )}
339
  </div>
340
  </div>
341