Skip to content

Commit 13b6809

Browse files
Merge pull request #4 from oriondesign2015/develop
Suporte ao provedor SMTP
2 parents 7940876 + add128f commit 13b6809

File tree

3 files changed

+148
-84
lines changed

3 files changed

+148
-84
lines changed

.env.example

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,22 @@ JWT_EXPIRATION_TIME=3600
3434
# Encryption key for API keys
3535
ENCRYPTION_KEY="your-encryption-key"
3636

37+
# Email provider settings
38+
EMAIL_PROVIDER="sendgrid"
39+
3740
# SendGrid
3841
SENDGRID_API_KEY="your-sendgrid-api-key"
3942
EMAIL_FROM="noreply@yourdomain.com"
43+
44+
# SMTP settings
45+
SMTP_HOST="your-smtp-host"
46+
SMTP_FROM="noreply-smtp@yourdomain.com"
47+
SMTP_USER="your-smtp-username"
48+
SMTP_PASSWORD="your-smtp-password"
49+
SMTP_PORT=587
50+
SMTP_USE_TLS=true
51+
SMTP_USE_SSL=false
52+
4053
APP_URL="https://yourdomain.com"
4154

4255
LANGFUSE_PUBLIC_KEY="your-langfuse-public-key"

src/config/settings.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,22 @@ class Settings(BaseSettings):
8181
# Encryption settings
8282
ENCRYPTION_KEY: str = os.getenv("ENCRYPTION_KEY", secrets.token_urlsafe(32))
8383

84+
# Email provider settings
85+
EMAIL_PROVIDER: str = os.getenv("EMAIL_PROVIDER", "sendgrid")
86+
8487
# SendGrid settings
8588
SENDGRID_API_KEY: str = os.getenv("SENDGRID_API_KEY", "")
8689
EMAIL_FROM: str = os.getenv("EMAIL_FROM", "noreply@yourdomain.com")
90+
91+
# SMTP settings
92+
SMTP_HOST: str = os.getenv("SMTP_HOST", "")
93+
SMTP_PORT: int = int(os.getenv("SMTP_PORT", 587))
94+
SMTP_USER: str = os.getenv("SMTP_USER", "")
95+
SMTP_PASSWORD: str = os.getenv("SMTP_PASSWORD", "")
96+
SMTP_USE_TLS: bool = os.getenv("SMTP_USE_TLS", "true").lower() == "true"
97+
SMTP_USE_SSL: bool = os.getenv("SMTP_USE_SSL", "false").lower() == "true"
98+
SMTP_FROM: str = os.getenv("SMTP_FROM", "")
99+
87100
APP_URL: str = os.getenv("APP_URL", "http://localhost:8000")
88101

89102
# Server settings

src/services/email_service.py

Lines changed: 122 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@
3333
from datetime import datetime
3434
from jinja2 import Environment, FileSystemLoader, select_autoescape
3535
import os
36+
import smtplib
37+
from email.mime.text import MIMEText
38+
from email.mime.multipart import MIMEMultipart
3639
from pathlib import Path
40+
from config.settings import settings
3741

3842
logger = 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+
70178
def 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

Comments
 (0)