33
33
from datetime import datetime
34
34
from jinja2 import Environment , FileSystemLoader , select_autoescape
35
35
import os
36
+ import smtplib
37
+ from email .mime .text import MIMEText
38
+ from email .mime .multipart import MIMEMultipart
36
39
from pathlib import Path
40
+ from config .settings import settings
37
41
38
42
logger = logging .getLogger (__name__ )
39
43
@@ -67,6 +71,110 @@ def _render_template(template_name: str, context: dict) -> str:
67
71
return f"<p>Could not display email content. Please access { context .get ('verification_link' , '' ) or context .get ('reset_link' , '' )} </p>"
68
72
69
73
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
+
70
178
def send_verification_email (email : str , token : str ) -> bool :
71
179
"""
72
180
Send a verification email to the user
@@ -79,40 +187,22 @@ def send_verification_email(email: str, token: str) -> bool:
79
187
bool: True if the email was sent successfully, False otherwise
80
188
"""
81
189
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 )
85
190
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 } "
88
192
89
193
html_content = _render_template (
90
194
"verification_email" ,
91
195
{
92
196
"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
96
198
"current_year" : datetime .now ().year ,
97
199
},
98
200
)
99
201
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 )
113
203
114
204
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 )} " )
116
206
return False
117
207
118
208
@@ -128,40 +218,22 @@ def send_password_reset_email(email: str, token: str) -> bool:
128
218
bool: True if the email was sent successfully, False otherwise
129
219
"""
130
220
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 )
134
221
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 } "
137
223
138
224
html_content = _render_template (
139
225
"password_reset" ,
140
226
{
141
227
"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
145
229
"current_year" : datetime .now ().year ,
146
230
},
147
231
)
148
232
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 )
162
234
163
235
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 )} " )
165
237
return False
166
238
167
239
@@ -177,12 +249,8 @@ def send_welcome_email(email: str, user_name: str = None) -> bool:
177
249
bool: True if the email was sent successfully, False otherwise
178
250
"""
179
251
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 )
183
252
subject = "Welcome to Evo AI"
184
-
185
- dashboard_link = f"{ os .getenv ('APP_URL' )} /dashboard"
253
+ dashboard_link = f"{ settings .APP_URL } /dashboard"
186
254
187
255
html_content = _render_template (
188
256
"welcome_email" ,
@@ -193,22 +261,10 @@ def send_welcome_email(email: str, user_name: str = None) -> bool:
193
261
},
194
262
)
195
263
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 )
209
265
210
266
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 )} " )
212
268
return False
213
269
214
270
@@ -228,14 +284,8 @@ def send_account_locked_email(
228
284
bool: True if the email was sent successfully, False otherwise
229
285
"""
230
286
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 )
234
287
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 } "
239
289
240
290
html_content = _render_template (
241
291
"account_locked" ,
@@ -248,20 +298,8 @@ def send_account_locked_email(
248
298
},
249
299
)
250
300
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 )
264
302
265
303
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 )} " )
267
305
return False
0 commit comments