saifisvibin commited on
Commit
bfdf549
·
1 Parent(s): 87ae9fd

Add email format and DNS validation to detect invalid emails before sending

Browse files
backend/routers/email.py CHANGED
@@ -8,6 +8,7 @@ from sendgrid.helpers.mail import Mail, Attachment, FileContent, FileName, FileT
8
  import base64
9
  import uuid
10
  from backend.utils.pdf_generator import generate_certificate_pdf
 
11
 
12
  router = APIRouter()
13
 
@@ -84,6 +85,32 @@ def send_email_task(
84
 
85
  if not name or not email:
86
  print(f"Skipping row with missing name or email")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  return
88
 
89
  print(f"Starting email task for: {name} ({email})")
 
8
  import base64
9
  import uuid
10
  from backend.utils.pdf_generator import generate_certificate_pdf
11
+ from backend.utils.email_validator import validate_email
12
 
13
  router = APIRouter()
14
 
 
85
 
86
  if not name or not email:
87
  print(f"Skipping row with missing name or email")
88
+ if job_id in email_progress:
89
+ email_progress[job_id]["sent"] += 1
90
+ email_progress[job_id]["failed"] += 1
91
+ if "failed_emails" not in email_progress[job_id]:
92
+ email_progress[job_id]["failed_emails"] = []
93
+ email_progress[job_id]["failed_emails"].append({
94
+ "email": email or "(empty)",
95
+ "name": name or "(empty)",
96
+ "error": "Missing name or email"
97
+ })
98
+ return
99
+
100
+ # Validate email format and domain BEFORE generating PDF
101
+ is_valid, validation_error = validate_email(email)
102
+ if not is_valid:
103
+ print(f"Invalid email skipped: {email} - {validation_error}")
104
+ if job_id in email_progress:
105
+ email_progress[job_id]["sent"] += 1
106
+ email_progress[job_id]["failed"] += 1
107
+ if "failed_emails" not in email_progress[job_id]:
108
+ email_progress[job_id]["failed_emails"] = []
109
+ email_progress[job_id]["failed_emails"].append({
110
+ "email": email,
111
+ "name": name,
112
+ "error": validation_error
113
+ })
114
  return
115
 
116
  print(f"Starting email task for: {name} ({email})")
backend/utils/email_validator.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Email validation utilities - Format and DNS validation
3
+ """
4
+ import re
5
+ import socket
6
+ from typing import Tuple
7
+
8
+ def validate_email_format(email: str) -> Tuple[bool, str]:
9
+ """
10
+ Validate email format using regex.
11
+ Returns (is_valid, error_message)
12
+ """
13
+ if not email or not email.strip():
14
+ return False, "Email is empty"
15
+
16
+ email = email.strip().lower()
17
+
18
+ # Basic email regex pattern
19
+ pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
20
+
21
+ if not re.match(pattern, email):
22
+ return False, f"Invalid email format: {email}"
23
+
24
+ return True, ""
25
+
26
+
27
+ def validate_email_domain(email: str) -> Tuple[bool, str]:
28
+ """
29
+ Validate that the email domain has valid MX or A records.
30
+ Returns (is_valid, error_message)
31
+ """
32
+ try:
33
+ domain = email.split('@')[1]
34
+
35
+ # Try to get MX records first
36
+ try:
37
+ socket.getaddrinfo(domain, None, socket.AF_INET)
38
+ return True, ""
39
+ except socket.gaierror:
40
+ pass
41
+
42
+ # If no A record, try with 'mail.' prefix (common mail server)
43
+ try:
44
+ socket.getaddrinfo(f"mail.{domain}", None, socket.AF_INET)
45
+ return True, ""
46
+ except socket.gaierror:
47
+ pass
48
+
49
+ return False, f"Domain does not exist: {domain}"
50
+
51
+ except Exception as e:
52
+ return False, f"Domain validation error: {str(e)}"
53
+
54
+
55
+ def validate_email(email: str) -> Tuple[bool, str]:
56
+ """
57
+ Full email validation - format and domain.
58
+ Returns (is_valid, error_message)
59
+ """
60
+ # First check format
61
+ is_valid, error = validate_email_format(email)
62
+ if not is_valid:
63
+ return False, error
64
+
65
+ # Then check domain
66
+ is_valid, error = validate_email_domain(email)
67
+ if not is_valid:
68
+ return False, error
69
+
70
+ return True, ""