Suporte ao provedor SMTP
Adiciona suporte ao envio de e-mails via protocolo SMTP, além do já existente provedor SendGrid. Agora é possível selecionar entre "sendgrid" ou "smtp" por meio da variável `EMAIL_PROVIDER`. Novas variáveis de ambiente: • EMAIL_PROVIDER="smtp" # ou sendgrid • SMTP_HOST="your-smtp-host" • SMTP_FROM="noreply-smtp@yourdomain.com" • SMTP_USER="your-smtp-username" • SMTP_PASSWORD="your-smtp-password" • SMTP_PORT=587 • SMTP_USE_TLS=true • SMTP_USE_SSL=false
This commit is contained in:
parent
958eeec4a6
commit
add128f4d5
13
.env.example
13
.env.example
@ -34,9 +34,22 @@ JWT_EXPIRATION_TIME=3600
|
|||||||
# Encryption key for API keys
|
# Encryption key for API keys
|
||||||
ENCRYPTION_KEY="your-encryption-key"
|
ENCRYPTION_KEY="your-encryption-key"
|
||||||
|
|
||||||
|
# Email provider settings
|
||||||
|
EMAIL_PROVIDER="sendgrid"
|
||||||
|
|
||||||
# SendGrid
|
# SendGrid
|
||||||
SENDGRID_API_KEY="your-sendgrid-api-key"
|
SENDGRID_API_KEY="your-sendgrid-api-key"
|
||||||
EMAIL_FROM="noreply@yourdomain.com"
|
EMAIL_FROM="noreply@yourdomain.com"
|
||||||
|
|
||||||
|
# SMTP settings
|
||||||
|
SMTP_HOST="your-smtp-host"
|
||||||
|
SMTP_FROM="noreply-smtp@yourdomain.com"
|
||||||
|
SMTP_USER="your-smtp-username"
|
||||||
|
SMTP_PASSWORD="your-smtp-password"
|
||||||
|
SMTP_PORT=587
|
||||||
|
SMTP_USE_TLS=true
|
||||||
|
SMTP_USE_SSL=false
|
||||||
|
|
||||||
APP_URL="https://yourdomain.com"
|
APP_URL="https://yourdomain.com"
|
||||||
|
|
||||||
LANGFUSE_PUBLIC_KEY="your-langfuse-public-key"
|
LANGFUSE_PUBLIC_KEY="your-langfuse-public-key"
|
||||||
|
@ -81,9 +81,22 @@ class Settings(BaseSettings):
|
|||||||
# Encryption settings
|
# Encryption settings
|
||||||
ENCRYPTION_KEY: str = os.getenv("ENCRYPTION_KEY", secrets.token_urlsafe(32))
|
ENCRYPTION_KEY: str = os.getenv("ENCRYPTION_KEY", secrets.token_urlsafe(32))
|
||||||
|
|
||||||
|
# Email provider settings
|
||||||
|
EMAIL_PROVIDER: str = os.getenv("EMAIL_PROVIDER", "sendgrid")
|
||||||
|
|
||||||
# SendGrid settings
|
# SendGrid settings
|
||||||
SENDGRID_API_KEY: str = os.getenv("SENDGRID_API_KEY", "")
|
SENDGRID_API_KEY: str = os.getenv("SENDGRID_API_KEY", "")
|
||||||
EMAIL_FROM: str = os.getenv("EMAIL_FROM", "noreply@yourdomain.com")
|
EMAIL_FROM: str = os.getenv("EMAIL_FROM", "noreply@yourdomain.com")
|
||||||
|
|
||||||
|
# SMTP settings
|
||||||
|
SMTP_HOST: str = os.getenv("SMTP_HOST", "")
|
||||||
|
SMTP_PORT: int = int(os.getenv("SMTP_PORT", 587))
|
||||||
|
SMTP_USER: str = os.getenv("SMTP_USER", "")
|
||||||
|
SMTP_PASSWORD: str = os.getenv("SMTP_PASSWORD", "")
|
||||||
|
SMTP_USE_TLS: bool = os.getenv("SMTP_USE_TLS", "true").lower() == "true"
|
||||||
|
SMTP_USE_SSL: bool = os.getenv("SMTP_USE_SSL", "false").lower() == "true"
|
||||||
|
SMTP_FROM: str = os.getenv("SMTP_FROM", "")
|
||||||
|
|
||||||
APP_URL: str = os.getenv("APP_URL", "http://localhost:8000")
|
APP_URL: str = os.getenv("APP_URL", "http://localhost:8000")
|
||||||
|
|
||||||
# Server settings
|
# Server settings
|
||||||
|
@ -33,7 +33,11 @@ import logging
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||||
import os
|
import os
|
||||||
|
import smtplib
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
from email.mime.multipart import MIMEMultipart
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from config.settings import settings
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -67,6 +71,110 @@ def _render_template(template_name: str, context: dict) -> str:
|
|||||||
return f"<p>Could not display email content. Please access {context.get('verification_link', '') or context.get('reset_link', '')}</p>"
|
return f"<p>Could not display email content. Please access {context.get('verification_link', '') or context.get('reset_link', '')}</p>"
|
||||||
|
|
||||||
|
|
||||||
|
def _send_email_sendgrid(to_email: str, subject: str, html_content: str) -> bool:
|
||||||
|
"""
|
||||||
|
Send an email using SendGrid provider
|
||||||
|
|
||||||
|
Args:
|
||||||
|
to_email: Recipient's email
|
||||||
|
subject: Email subject
|
||||||
|
html_content: HTML content of the email
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if the email was sent successfully, False otherwise
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
sg = sendgrid.SendGridAPIClient(api_key=settings.SENDGRID_API_KEY)
|
||||||
|
from_email = Email(settings.EMAIL_FROM)
|
||||||
|
to_email = To(to_email)
|
||||||
|
content = Content("text/html", html_content)
|
||||||
|
|
||||||
|
mail = Mail(from_email, to_email, subject, content)
|
||||||
|
response = sg.client.mail.send.post(request_body=mail.get())
|
||||||
|
|
||||||
|
if response.status_code >= 200 and response.status_code < 300:
|
||||||
|
logger.info(f"Email sent via SendGrid to {to_email}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.error(
|
||||||
|
f"Failed to send email via SendGrid to {to_email}. Status: {response.status_code}"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error sending email via SendGrid to {to_email}: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _send_email_smtp(to_email: str, subject: str, html_content: str) -> bool:
|
||||||
|
"""
|
||||||
|
Send an email using SMTP provider
|
||||||
|
|
||||||
|
Args:
|
||||||
|
to_email: Recipient's email
|
||||||
|
subject: Email subject
|
||||||
|
html_content: HTML content of the email
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if the email was sent successfully, False otherwise
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Create message container
|
||||||
|
msg = MIMEMultipart('alternative')
|
||||||
|
msg['Subject'] = subject
|
||||||
|
msg['From'] = settings.SMTP_FROM or settings.EMAIL_FROM
|
||||||
|
msg['To'] = to_email
|
||||||
|
|
||||||
|
# Attach HTML content
|
||||||
|
part = MIMEText(html_content, 'html')
|
||||||
|
msg.attach(part)
|
||||||
|
|
||||||
|
# Setup SMTP server
|
||||||
|
if settings.SMTP_USE_SSL:
|
||||||
|
server = smtplib.SMTP_SSL(settings.SMTP_HOST, settings.SMTP_PORT)
|
||||||
|
else:
|
||||||
|
server = smtplib.SMTP(settings.SMTP_HOST, settings.SMTP_PORT)
|
||||||
|
if settings.SMTP_USE_TLS:
|
||||||
|
server.starttls()
|
||||||
|
|
||||||
|
# Login if credentials are provided
|
||||||
|
if settings.SMTP_USER and settings.SMTP_PASSWORD:
|
||||||
|
server.login(settings.SMTP_USER, settings.SMTP_PASSWORD)
|
||||||
|
|
||||||
|
# Send email
|
||||||
|
server.sendmail(
|
||||||
|
settings.SMTP_FROM or settings.EMAIL_FROM,
|
||||||
|
to_email,
|
||||||
|
msg.as_string()
|
||||||
|
)
|
||||||
|
server.quit()
|
||||||
|
|
||||||
|
logger.info(f"Email sent via SMTP to {to_email}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error sending email via SMTP to {to_email}: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def send_email(to_email: str, subject: str, html_content: str) -> bool:
|
||||||
|
"""
|
||||||
|
Send an email using the configured provider
|
||||||
|
|
||||||
|
Args:
|
||||||
|
to_email: Recipient's email
|
||||||
|
subject: Email subject
|
||||||
|
html_content: HTML content of the email
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if the email was sent successfully, False otherwise
|
||||||
|
"""
|
||||||
|
if settings.EMAIL_PROVIDER.lower() == "smtp":
|
||||||
|
return _send_email_smtp(to_email, subject, html_content)
|
||||||
|
else: # Default to SendGrid
|
||||||
|
return _send_email_sendgrid(to_email, subject, html_content)
|
||||||
|
|
||||||
|
|
||||||
def send_verification_email(email: str, token: str) -> bool:
|
def send_verification_email(email: str, token: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Send a verification email to the user
|
Send a verification email to the user
|
||||||
@ -79,40 +187,22 @@ def send_verification_email(email: str, token: str) -> bool:
|
|||||||
bool: True if the email was sent successfully, False otherwise
|
bool: True if the email was sent successfully, False otherwise
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
sg = sendgrid.SendGridAPIClient(api_key=os.getenv("SENDGRID_API_KEY"))
|
|
||||||
from_email = Email(os.getenv("EMAIL_FROM"))
|
|
||||||
to_email = To(email)
|
|
||||||
subject = "Email Verification - Evo AI"
|
subject = "Email Verification - Evo AI"
|
||||||
|
verification_link = f"{settings.APP_URL}/security/verify-email?code={token}"
|
||||||
verification_link = f"{os.getenv('APP_URL')}/security/verify-email?code={token}"
|
|
||||||
|
|
||||||
html_content = _render_template(
|
html_content = _render_template(
|
||||||
"verification_email",
|
"verification_email",
|
||||||
{
|
{
|
||||||
"verification_link": verification_link,
|
"verification_link": verification_link,
|
||||||
"user_name": email.split("@")[
|
"user_name": email.split("@")[0], # Use part of the email as temporary name
|
||||||
0
|
|
||||||
], # Use part of the email as temporary name
|
|
||||||
"current_year": datetime.now().year,
|
"current_year": datetime.now().year,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
content = Content("text/html", html_content)
|
return send_email(email, subject, html_content)
|
||||||
|
|
||||||
mail = Mail(from_email, to_email, subject, content)
|
|
||||||
response = sg.client.mail.send.post(request_body=mail.get())
|
|
||||||
|
|
||||||
if response.status_code >= 200 and response.status_code < 300:
|
|
||||||
logger.info(f"Verification email sent to {email}")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
logger.error(
|
|
||||||
f"Failed to send verification email to {email}. Status: {response.status_code}"
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error sending verification email to {email}: {str(e)}")
|
logger.error(f"Error preparing verification email to {email}: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@ -128,40 +218,22 @@ def send_password_reset_email(email: str, token: str) -> bool:
|
|||||||
bool: True if the email was sent successfully, False otherwise
|
bool: True if the email was sent successfully, False otherwise
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
sg = sendgrid.SendGridAPIClient(api_key=os.getenv("SENDGRID_API_KEY"))
|
|
||||||
from_email = Email(os.getenv("EMAIL_FROM"))
|
|
||||||
to_email = To(email)
|
|
||||||
subject = "Password Reset - Evo AI"
|
subject = "Password Reset - Evo AI"
|
||||||
|
reset_link = f"{settings.APP_URL}/security/reset-password?token={token}"
|
||||||
reset_link = f"{os.getenv('APP_URL')}/security/reset-password?token={token}"
|
|
||||||
|
|
||||||
html_content = _render_template(
|
html_content = _render_template(
|
||||||
"password_reset",
|
"password_reset",
|
||||||
{
|
{
|
||||||
"reset_link": reset_link,
|
"reset_link": reset_link,
|
||||||
"user_name": email.split("@")[
|
"user_name": email.split("@")[0], # Use part of the email as temporary name
|
||||||
0
|
|
||||||
], # Use part of the email as temporary name
|
|
||||||
"current_year": datetime.now().year,
|
"current_year": datetime.now().year,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
content = Content("text/html", html_content)
|
return send_email(email, subject, html_content)
|
||||||
|
|
||||||
mail = Mail(from_email, to_email, subject, content)
|
|
||||||
response = sg.client.mail.send.post(request_body=mail.get())
|
|
||||||
|
|
||||||
if response.status_code >= 200 and response.status_code < 300:
|
|
||||||
logger.info(f"Password reset email sent to {email}")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
logger.error(
|
|
||||||
f"Failed to send password reset email to {email}. Status: {response.status_code}"
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error sending password reset email to {email}: {str(e)}")
|
logger.error(f"Error preparing password reset email to {email}: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@ -177,12 +249,8 @@ def send_welcome_email(email: str, user_name: str = None) -> bool:
|
|||||||
bool: True if the email was sent successfully, False otherwise
|
bool: True if the email was sent successfully, False otherwise
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
sg = sendgrid.SendGridAPIClient(api_key=os.getenv("SENDGRID_API_KEY"))
|
|
||||||
from_email = Email(os.getenv("EMAIL_FROM"))
|
|
||||||
to_email = To(email)
|
|
||||||
subject = "Welcome to Evo AI"
|
subject = "Welcome to Evo AI"
|
||||||
|
dashboard_link = f"{settings.APP_URL}/dashboard"
|
||||||
dashboard_link = f"{os.getenv('APP_URL')}/dashboard"
|
|
||||||
|
|
||||||
html_content = _render_template(
|
html_content = _render_template(
|
||||||
"welcome_email",
|
"welcome_email",
|
||||||
@ -193,22 +261,10 @@ def send_welcome_email(email: str, user_name: str = None) -> bool:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
content = Content("text/html", html_content)
|
return send_email(email, subject, html_content)
|
||||||
|
|
||||||
mail = Mail(from_email, to_email, subject, content)
|
|
||||||
response = sg.client.mail.send.post(request_body=mail.get())
|
|
||||||
|
|
||||||
if response.status_code >= 200 and response.status_code < 300:
|
|
||||||
logger.info(f"Welcome email sent to {email}")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
logger.error(
|
|
||||||
f"Failed to send welcome email to {email}. Status: {response.status_code}"
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error sending welcome email to {email}: {str(e)}")
|
logger.error(f"Error preparing welcome email to {email}: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@ -228,14 +284,8 @@ def send_account_locked_email(
|
|||||||
bool: True if the email was sent successfully, False otherwise
|
bool: True if the email was sent successfully, False otherwise
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
sg = sendgrid.SendGridAPIClient(api_key=os.getenv("SENDGRID_API_KEY"))
|
|
||||||
from_email = Email(os.getenv("EMAIL_FROM"))
|
|
||||||
to_email = To(email)
|
|
||||||
subject = "Security Alert - Account Locked"
|
subject = "Security Alert - Account Locked"
|
||||||
|
reset_link = f"{settings.APP_URL}/security/reset-password?token={reset_token}"
|
||||||
reset_link = (
|
|
||||||
f"{os.getenv('APP_URL')}/security/reset-password?token={reset_token}"
|
|
||||||
)
|
|
||||||
|
|
||||||
html_content = _render_template(
|
html_content = _render_template(
|
||||||
"account_locked",
|
"account_locked",
|
||||||
@ -248,20 +298,8 @@ def send_account_locked_email(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
content = Content("text/html", html_content)
|
return send_email(email, subject, html_content)
|
||||||
|
|
||||||
mail = Mail(from_email, to_email, subject, content)
|
|
||||||
response = sg.client.mail.send.post(request_body=mail.get())
|
|
||||||
|
|
||||||
if response.status_code >= 200 and response.status_code < 300:
|
|
||||||
logger.info(f"Account locked email sent to {email}")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
logger.error(
|
|
||||||
f"Failed to send account locked email to {email}. Status: {response.status_code}"
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error sending account locked email to {email}: {str(e)}")
|
logger.error(f"Error preparing account locked email to {email}: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
Loading…
Reference in New Issue
Block a user