93 lines
3.2 KiB
Python
93 lines
3.2 KiB
Python
|
|
"""SMTP email provider implementation."""
|
||
|
|
import logging
|
||
|
|
import smtplib
|
||
|
|
from email.mime.multipart import MIMEMultipart
|
||
|
|
from email.mime.text import MIMEText
|
||
|
|
|
||
|
|
from flask import current_app
|
||
|
|
|
||
|
|
from gatehouse_app.services.email_provider import EmailMessage, EmailProvider
|
||
|
|
|
||
|
|
logger = logging.getLogger(__name__)
|
||
|
|
|
||
|
|
|
||
|
|
class SmtpEmailProvider(EmailProvider):
|
||
|
|
"""SMTP-based email provider implementation."""
|
||
|
|
|
||
|
|
# Configuration keys
|
||
|
|
EMAIL_ENABLED_KEY = "EMAIL_ENABLED"
|
||
|
|
SMTP_HOST_KEY = "SMTP_HOST"
|
||
|
|
SMTP_PORT_KEY = "SMTP_PORT"
|
||
|
|
SMTP_USERNAME_KEY = "SMTP_USERNAME"
|
||
|
|
SMTP_PASSWORD_KEY = "SMTP_PASSWORD"
|
||
|
|
SMTP_USE_TLS_KEY = "SMTP_USE_TLS"
|
||
|
|
FROM_ADDRESS_KEY = "FROM_ADDRESS"
|
||
|
|
|
||
|
|
def send(self, message: EmailMessage) -> bool:
|
||
|
|
"""Send an email via SMTP.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
message: EmailMessage instance containing email details
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
bool: True if email was sent successfully, False otherwise
|
||
|
|
"""
|
||
|
|
email_enabled = current_app.config.get(self.EMAIL_ENABLED_KEY, False)
|
||
|
|
if not email_enabled:
|
||
|
|
logger.info(
|
||
|
|
f"[EMAIL DISABLED] Would have sent to: {message.to} | "
|
||
|
|
f"Subject: {message.subject}"
|
||
|
|
)
|
||
|
|
return False
|
||
|
|
|
||
|
|
smtp_host = current_app.config.get(self.SMTP_HOST_KEY, "")
|
||
|
|
from_address = message.from_address or current_app.config.get(self.FROM_ADDRESS_KEY, "")
|
||
|
|
|
||
|
|
missing = [k for k, v in [("SMTP_HOST", smtp_host), ("FROM_ADDRESS", from_address)] if not v]
|
||
|
|
if missing:
|
||
|
|
logger.error(
|
||
|
|
f"[EMAIL] Cannot send — missing config: {', '.join(missing)}. "
|
||
|
|
f"Would have sent to: {message.to} | Subject: {message.subject}"
|
||
|
|
)
|
||
|
|
return False
|
||
|
|
|
||
|
|
smtp_port_raw = current_app.config.get(self.SMTP_PORT_KEY, 587)
|
||
|
|
try:
|
||
|
|
smtp_port = int(smtp_port_raw)
|
||
|
|
except (TypeError, ValueError):
|
||
|
|
logger.error(f"[EMAIL] Invalid SMTP_PORT value: {smtp_port_raw!r}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
smtp_username = current_app.config.get(self.SMTP_USERNAME_KEY)
|
||
|
|
smtp_password = current_app.config.get(self.SMTP_PASSWORD_KEY)
|
||
|
|
|
||
|
|
smtp_use_tls = current_app.config.get(
|
||
|
|
self.SMTP_USE_TLS_KEY,
|
||
|
|
smtp_port not in (25, 1025),
|
||
|
|
)
|
||
|
|
|
||
|
|
try:
|
||
|
|
msg = MIMEMultipart("alternative")
|
||
|
|
msg["Subject"] = message.subject
|
||
|
|
msg["From"] = from_address
|
||
|
|
msg["To"] = message.to
|
||
|
|
msg.attach(MIMEText(message.body, "plain"))
|
||
|
|
if message.html_body:
|
||
|
|
msg.attach(MIMEText(message.html_body, "html"))
|
||
|
|
|
||
|
|
with smtplib.SMTP(smtp_host, smtp_port) as server:
|
||
|
|
server.ehlo()
|
||
|
|
if smtp_use_tls:
|
||
|
|
server.starttls()
|
||
|
|
server.ehlo()
|
||
|
|
if smtp_username and smtp_password:
|
||
|
|
server.login(smtp_username, smtp_password)
|
||
|
|
server.send_message(msg)
|
||
|
|
|
||
|
|
logger.info(f"[EMAIL] Sent to {message.to} | Subject: {message.subject}")
|
||
|
|
return True
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
logger.error(f"[EMAIL] Failed to send to {message.to}: {e}")
|
||
|
|
return False
|