infrasynth-backend-kit/infrasynth/notifications/channels/email_smtp.py
2026-08-28 14:38:47 -05:00

99 lines
3 KiB
Python

import logging
from django.conf import settings
from django.core.mail import EmailMessage, get_connection
from infrasynth.shared.enums import ChannelType
from infrasynth.shared.results import Result
from infrasynth.shared.settings_utils import get_setting
from .base import BaseChannel
logger = logging.getLogger(__name__)
class SMTPChannel(BaseChannel):
"""Email channel that sends via a configurable SMTP backend.
Configuration keys (read from ``ChannelConfig.config``):
- ``host``, ``port``, ``username``, ``password``
- ``use_tls``, ``use_ssl``, ``timeout``
- ``backend`` (dotted path, defaults to Django's SMTP backend)
- ``from_email`` (falls back to DEFAULT_FROM_EMAIL / INFRASYNTH_NOTIFICATIONS)
"""
channel_type = ChannelType.EMAIL
def __init__(
self,
host=None,
port=None,
username=None,
password=None,
use_tls=True,
use_ssl=False,
timeout=30,
backend=None,
from_email=None,
):
self.host = host
self.port = port or 587
self.username = username
self.password = password
self.use_tls = use_tls
self.use_ssl = use_ssl
self.timeout = timeout
self.backend = backend or "django.core.mail.backends.smtp.EmailBackend"
self.from_email = (
from_email
or getattr(settings, "DEFAULT_FROM_EMAIL", None)
or get_setting("INFRASYNTH_NOTIFICATIONS", "DEFAULT_FROM_EMAIL", "noreply@example.com")
)
@classmethod
def from_config(cls, config: dict) -> "SMTPChannel":
config = {str(key).lower(): value for key, value in (config or {}).items()}
return cls(**config)
def send(
self,
recipient: str,
subject: str,
body: str,
is_html: bool = True,
attachments=None,
) -> Result[bool, str]:
if not self.host:
return Result.err("SMTP host not configured")
try:
message = EmailMessage(
subject=subject,
body=body,
from_email=self.from_email,
to=[recipient],
connection=self._get_connection(),
)
if is_html:
message.content_subtype = "html"
for attachment in attachments or []:
message.attach(attachment.filename, attachment.content, attachment.mime_type)
message.send()
except Exception as exc: # noqa: BLE001
logger.exception("SMTP send failed for %s", recipient)
return Result.err(str(exc))
return Result.ok(True)
def health_check(self) -> bool:
return bool(self.host)
def _get_connection(self):
return get_connection(
backend=self.backend,
host=self.host or None,
port=self.port,
username=self.username or "",
password=self.password or "",
use_tls=self.use_tls,
use_ssl=self.use_ssl,
timeout=self.timeout,
)