SMTP and other email realted settings

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-08 06:45:30 +00:00
parent 3651ee9ed4
commit d4ecddba22
7 changed files with 499 additions and 5 deletions

View File

@@ -2,15 +2,22 @@
Email Service - Multi-provider email sending
Uses Resend for transactional emails with fallback to Django's send_mail.
Also supports direct SMTP configuration.
Supports template rendering and multiple email types.
Configuration stored in IntegrationProvider model (provider_id='resend')
and EmailSettings model for SMTP and provider selection.
"""
import hashlib
import hmac
import logging
import re
import smtplib
import time
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
from typing import Optional, List, Dict, Any
from urllib.parse import urlencode
from django.core.mail import send_mail
@@ -162,9 +169,10 @@ class EmailService:
Unified email service supporting multiple providers.
Primary: Resend (for production transactional emails)
Alternative: SMTP (configurable via EmailSettings)
Fallback: Django's send_mail (uses EMAIL_BACKEND from settings)
Uses EmailSettings model for configuration (from_email, from_name, etc.)
Uses EmailSettings model for configuration (from_email, from_name, provider selection, SMTP settings)
"""
def __init__(self):
@@ -172,6 +180,8 @@ class EmailService:
self._resend_config = {}
self._brevo_configured = False
self._brevo_config = {}
self._smtp_configured = False
self._smtp_config = {}
self._email_settings = None
self._setup_providers()
@@ -193,6 +203,22 @@ class EmailService:
else:
logger.info("Resend provider not configured in IntegrationProvider")
# Setup SMTP from EmailSettings
if self._email_settings:
smtp_host = self._email_settings.smtp_host
if smtp_host:
self._smtp_config = {
'host': smtp_host,
'port': self._email_settings.smtp_port,
'username': self._email_settings.smtp_username,
'password': self._email_settings.smtp_password,
'use_tls': self._email_settings.smtp_use_tls,
'use_ssl': self._email_settings.smtp_use_ssl,
'timeout': self._email_settings.smtp_timeout,
}
self._smtp_configured = True
logger.info(f"SMTP email provider initialized: {smtp_host}:{self._email_settings.smtp_port}")
# Setup Brevo (future - for marketing emails)
brevo_provider = IntegrationProvider.get_provider('brevo')
if brevo_provider and brevo_provider.api_key:
@@ -200,6 +226,14 @@ class EmailService:
self._brevo_configured = True
logger.info("Brevo email provider initialized")
@property
def email_provider(self) -> str:
"""Get selected email provider from EmailSettings"""
settings_obj = self._get_settings()
if settings_obj and settings_obj.email_provider:
return settings_obj.email_provider
return 'resend' # Default to resend
def _get_settings(self):
"""Get fresh email settings (refreshes on each call)"""
if not self._email_settings:
@@ -373,8 +407,32 @@ class EmailService:
sender_email = from_email or self.from_email
from_address = f"{sender_name} <{sender_email}>"
# Try Resend first
if self._resend_configured:
# Select email provider based on EmailSettings configuration
selected_provider = self.email_provider
if selected_provider == 'smtp' and self._smtp_configured:
return self._send_via_smtp(
to=to,
subject=subject,
html=html,
text=text,
from_address=from_address,
reply_to=reply_to or self.reply_to,
attachments=attachments,
)
elif selected_provider == 'resend' and self._resend_configured:
return self._send_via_resend(
to=to,
subject=subject,
html=html,
text=text,
from_address=from_address,
reply_to=reply_to or self.reply_to,
attachments=attachments,
tags=tags,
)
elif self._resend_configured:
# Fallback to Resend if it's configured
return self._send_via_resend(
to=to,
subject=subject,
@@ -465,6 +523,101 @@ class EmailService:
from_email=from_address.split('<')[-1].rstrip('>'),
)
def _send_via_smtp(
self,
to: List[str],
subject: str,
html: Optional[str],
text: Optional[str],
from_address: str,
reply_to: Optional[str],
attachments: Optional[List[Dict]],
) -> Dict[str, Any]:
"""Send email via direct SMTP connection"""
try:
# Create message
msg = MIMEMultipart('alternative')
msg['Subject'] = subject
msg['From'] = from_address
msg['To'] = ', '.join(to)
if reply_to:
msg['Reply-To'] = reply_to
# Add custom headers
msg['X-Mailer'] = 'IGNY8 Email Service'
# Attach text version
if text:
part1 = MIMEText(text, 'plain', 'utf-8')
msg.attach(part1)
# Attach HTML version
if html:
part2 = MIMEText(html, 'html', 'utf-8')
msg.attach(part2)
# Handle attachments
if attachments:
for attachment in attachments:
part = MIMEBase('application', 'octet-stream')
content = attachment.get('content', b'')
if isinstance(content, str):
content = content.encode('utf-8')
part.set_payload(content)
encoders.encode_base64(part)
filename = attachment.get('filename', 'attachment')
part.add_header('Content-Disposition', f'attachment; filename="{filename}"')
msg.attach(part)
# Get SMTP configuration
smtp_host = self._smtp_config.get('host')
smtp_port = self._smtp_config.get('port', 587)
smtp_username = self._smtp_config.get('username')
smtp_password = self._smtp_config.get('password')
use_tls = self._smtp_config.get('use_tls', True)
use_ssl = self._smtp_config.get('use_ssl', False)
timeout = self._smtp_config.get('timeout', 30)
# Connect and send
if use_ssl:
server = smtplib.SMTP_SSL(smtp_host, smtp_port, timeout=timeout)
else:
server = smtplib.SMTP(smtp_host, smtp_port, timeout=timeout)
if use_tls:
server.starttls()
if smtp_username and smtp_password:
server.login(smtp_username, smtp_password)
server.sendmail(
from_address.split('<')[-1].rstrip('>'),
to,
msg.as_string()
)
server.quit()
logger.info(f"Email sent via SMTP: {subject} to {to}")
return {
'success': True,
'id': None, # SMTP doesn't provide message IDs like Resend
'provider': 'smtp',
}
except Exception as e:
logger.error(f"Failed to send email via SMTP: {str(e)}")
# Fallback to Django mail
logger.info("Falling back to Django mail backend")
return self._send_via_django(
to=to,
subject=subject,
html=html,
text=text,
from_email=from_address.split('<')[-1].rstrip('>'),
)
def _send_via_django(
self,
to: List[str],