3333from datetime import datetime
3434from jinja2 import Environment , FileSystemLoader , select_autoescape
3535import os
36+ import smtplib
37+ from email .mime .text import MIMEText
38+ from email .mime .multipart import MIMEMultipart
3639from pathlib import Path
40+ from config .settings import settings
3741
3842logger = logging .getLogger (__name__ )
3943
@@ -67,6 +71,110 @@ def _render_template(template_name: str, context: dict) -> str:
6771 return f"<p>Could not display email content. Please access { context .get ('verification_link' , '' ) or context .get ('reset_link' , '' )} </p>"
6872
6973
74+ def _send_email_sendgrid (to_email : str , subject : str , html_content : str ) -> bool :
75+ """
76+ Send an email using SendGrid provider
77+
78+ Args:
79+ to_email: Recipient's email
80+ subject: Email subject
81+ html_content: HTML content of the email
82+
83+ Returns:
84+ bool: True if the email was sent successfully, False otherwise
85+ """
86+ try :
87+ sg = sendgrid .SendGridAPIClient (api_key = settings .SENDGRID_API_KEY )
88+ from_email = Email (settings .EMAIL_FROM )
89+ to_email = To (to_email )
90+ content = Content ("text/html" , html_content )
91+
92+ mail = Mail (from_email , to_email , subject , content )
93+ response = sg .client .mail .send .post (request_body = mail .get ())
94+
95+ if response .status_code >= 200 and response .status_code < 300 :
96+ logger .info (f"Email sent via SendGrid to { to_email } " )
97+ return True
98+ else :
99+ logger .error (
100+ f"Failed to send email via SendGrid to { to_email } . Status: { response .status_code } "
101+ )
102+ return False
103+
104+ except Exception as e :
105+ logger .error (f"Error sending email via SendGrid to { to_email } : { str (e )} " )
106+ return False
107+
108+
109+ def _send_email_smtp (to_email : str , subject : str , html_content : str ) -> bool :
110+ """
111+ Send an email using SMTP provider
112+
113+ Args:
114+ to_email: Recipient's email
115+ subject: Email subject
116+ html_content: HTML content of the email
117+
118+ Returns:
119+ bool: True if the email was sent successfully, False otherwise
120+ """
121+ try :
122+ # Create message container
123+ msg = MIMEMultipart ('alternative' )
124+ msg ['Subject' ] = subject
125+ msg ['From' ] = settings .SMTP_FROM or settings .EMAIL_FROM
126+ msg ['To' ] = to_email
127+
128+ # Attach HTML content
129+ part = MIMEText (html_content , 'html' )
130+ msg .attach (part )
131+
132+ # Setup SMTP server
133+ if settings .SMTP_USE_SSL :
134+ server = smtplib .SMTP_SSL (settings .SMTP_HOST , settings .SMTP_PORT )
135+ else :
136+ server = smtplib .SMTP (settings .SMTP_HOST , settings .SMTP_PORT )
137+ if settings .SMTP_USE_TLS :
138+ server .starttls ()
139+
140+ # Login if credentials are provided
141+ if settings .SMTP_USER and settings .SMTP_PASSWORD :
142+ server .login (settings .SMTP_USER , settings .SMTP_PASSWORD )
143+
144+ # Send email
145+ server .sendmail (
146+ settings .SMTP_FROM or settings .EMAIL_FROM ,
147+ to_email ,
148+ msg .as_string ()
149+ )
150+ server .quit ()
151+
152+ logger .info (f"Email sent via SMTP to { to_email } " )
153+ return True
154+
155+ except Exception as e :
156+ logger .error (f"Error sending email via SMTP to { to_email } : { str (e )} " )
157+ return False
158+
159+
160+ def send_email (to_email : str , subject : str , html_content : str ) -> bool :
161+ """
162+ Send an email using the configured provider
163+
164+ Args:
165+ to_email: Recipient's email
166+ subject: Email subject
167+ html_content: HTML content of the email
168+
169+ Returns:
170+ bool: True if the email was sent successfully, False otherwise
171+ """
172+ if settings .EMAIL_PROVIDER .lower () == "smtp" :
173+ return _send_email_smtp (to_email , subject , html_content )
174+ else : # Default to SendGrid
175+ return _send_email_sendgrid (to_email , subject , html_content )
176+
177+
70178def send_verification_email (email : str , token : str ) -> bool :
71179 """
72180 Send a verification email to the user
@@ -79,40 +187,22 @@ def send_verification_email(email: str, token: str) -> bool:
79187 bool: True if the email was sent successfully, False otherwise
80188 """
81189 try :
82- sg = sendgrid .SendGridAPIClient (api_key = os .getenv ("SENDGRID_API_KEY" ))
83- from_email = Email (os .getenv ("EMAIL_FROM" ))
84- to_email = To (email )
85190 subject = "Email Verification - Evo AI"
86-
87- verification_link = f"{ os .getenv ('APP_URL' )} /security/verify-email?code={ token } "
191+ verification_link = f"{ settings .APP_URL } /security/verify-email?code={ token } "
88192
89193 html_content = _render_template (
90194 "verification_email" ,
91195 {
92196 "verification_link" : verification_link ,
93- "user_name" : email .split ("@" )[
94- 0
95- ], # Use part of the email as temporary name
197+ "user_name" : email .split ("@" )[0 ], # Use part of the email as temporary name
96198 "current_year" : datetime .now ().year ,
97199 },
98200 )
99201
100- content = Content ("text/html" , html_content )
101-
102- mail = Mail (from_email , to_email , subject , content )
103- response = sg .client .mail .send .post (request_body = mail .get ())
104-
105- if response .status_code >= 200 and response .status_code < 300 :
106- logger .info (f"Verification email sent to { email } " )
107- return True
108- else :
109- logger .error (
110- f"Failed to send verification email to { email } . Status: { response .status_code } "
111- )
112- return False
202+ return send_email (email , subject , html_content )
113203
114204 except Exception as e :
115- logger .error (f"Error sending verification email to { email } : { str (e )} " )
205+ logger .error (f"Error preparing verification email to { email } : { str (e )} " )
116206 return False
117207
118208
@@ -128,40 +218,22 @@ def send_password_reset_email(email: str, token: str) -> bool:
128218 bool: True if the email was sent successfully, False otherwise
129219 """
130220 try :
131- sg = sendgrid .SendGridAPIClient (api_key = os .getenv ("SENDGRID_API_KEY" ))
132- from_email = Email (os .getenv ("EMAIL_FROM" ))
133- to_email = To (email )
134221 subject = "Password Reset - Evo AI"
135-
136- reset_link = f"{ os .getenv ('APP_URL' )} /security/reset-password?token={ token } "
222+ reset_link = f"{ settings .APP_URL } /security/reset-password?token={ token } "
137223
138224 html_content = _render_template (
139225 "password_reset" ,
140226 {
141227 "reset_link" : reset_link ,
142- "user_name" : email .split ("@" )[
143- 0
144- ], # Use part of the email as temporary name
228+ "user_name" : email .split ("@" )[0 ], # Use part of the email as temporary name
145229 "current_year" : datetime .now ().year ,
146230 },
147231 )
148232
149- content = Content ("text/html" , html_content )
150-
151- mail = Mail (from_email , to_email , subject , content )
152- response = sg .client .mail .send .post (request_body = mail .get ())
153-
154- if response .status_code >= 200 and response .status_code < 300 :
155- logger .info (f"Password reset email sent to { email } " )
156- return True
157- else :
158- logger .error (
159- f"Failed to send password reset email to { email } . Status: { response .status_code } "
160- )
161- return False
233+ return send_email (email , subject , html_content )
162234
163235 except Exception as e :
164- logger .error (f"Error sending password reset email to { email } : { str (e )} " )
236+ logger .error (f"Error preparing password reset email to { email } : { str (e )} " )
165237 return False
166238
167239
@@ -177,12 +249,8 @@ def send_welcome_email(email: str, user_name: str = None) -> bool:
177249 bool: True if the email was sent successfully, False otherwise
178250 """
179251 try :
180- sg = sendgrid .SendGridAPIClient (api_key = os .getenv ("SENDGRID_API_KEY" ))
181- from_email = Email (os .getenv ("EMAIL_FROM" ))
182- to_email = To (email )
183252 subject = "Welcome to Evo AI"
184-
185- dashboard_link = f"{ os .getenv ('APP_URL' )} /dashboard"
253+ dashboard_link = f"{ settings .APP_URL } /dashboard"
186254
187255 html_content = _render_template (
188256 "welcome_email" ,
@@ -193,22 +261,10 @@ def send_welcome_email(email: str, user_name: str = None) -> bool:
193261 },
194262 )
195263
196- content = Content ("text/html" , html_content )
197-
198- mail = Mail (from_email , to_email , subject , content )
199- response = sg .client .mail .send .post (request_body = mail .get ())
200-
201- if response .status_code >= 200 and response .status_code < 300 :
202- logger .info (f"Welcome email sent to { email } " )
203- return True
204- else :
205- logger .error (
206- f"Failed to send welcome email to { email } . Status: { response .status_code } "
207- )
208- return False
264+ return send_email (email , subject , html_content )
209265
210266 except Exception as e :
211- logger .error (f"Error sending welcome email to { email } : { str (e )} " )
267+ logger .error (f"Error preparing welcome email to { email } : { str (e )} " )
212268 return False
213269
214270
@@ -228,14 +284,8 @@ def send_account_locked_email(
228284 bool: True if the email was sent successfully, False otherwise
229285 """
230286 try :
231- sg = sendgrid .SendGridAPIClient (api_key = os .getenv ("SENDGRID_API_KEY" ))
232- from_email = Email (os .getenv ("EMAIL_FROM" ))
233- to_email = To (email )
234287 subject = "Security Alert - Account Locked"
235-
236- reset_link = (
237- f"{ os .getenv ('APP_URL' )} /security/reset-password?token={ reset_token } "
238- )
288+ reset_link = f"{ settings .APP_URL } /security/reset-password?token={ reset_token } "
239289
240290 html_content = _render_template (
241291 "account_locked" ,
@@ -248,20 +298,8 @@ def send_account_locked_email(
248298 },
249299 )
250300
251- content = Content ("text/html" , html_content )
252-
253- mail = Mail (from_email , to_email , subject , content )
254- response = sg .client .mail .send .post (request_body = mail .get ())
255-
256- if response .status_code >= 200 and response .status_code < 300 :
257- logger .info (f"Account locked email sent to { email } " )
258- return True
259- else :
260- logger .error (
261- f"Failed to send account locked email to { email } . Status: { response .status_code } "
262- )
263- return False
301+ return send_email (email , subject , html_content )
264302
265303 except Exception as e :
266- logger .error (f"Error sending account locked email to { email } : { str (e )} " )
304+ logger .error (f"Error preparing account locked email to { email } : { str (e )} " )
267305 return False
0 commit comments