"""Email utility — sends outbound marketing emails via Hetzner Postfix relay."""
import logging
from flask import current_app
from flask_mail import Message
from app import mail
logger = logging.getLogger(__name__)
def send_email(
to: list,
subject: str,
html_body: str = None,
text_body: str = None,
sender: str = None,
cc: list = None,
bcc: list = None,
):
"""Send an email via Flask-Mail.
Args:
to: List of recipient email addresses.
subject: Email subject line.
html_body: HTML email body.
text_body: Plain text email body.
sender: Override sender address (defaults to MAIL_DEFAULT_SENDER).
cc: List of CC recipients.
bcc: List of BCC recipients.
Returns:
dict with 'success' boolean and 'error' message if failed.
"""
try:
msg = Message(
subject=subject,
recipients=to,
html=html_body,
body=text_body,
sender=sender,
cc=cc,
bcc=bcc,
)
mail.send(msg)
logger.info("Email sent to %s: %s", to, subject)
return {'success': True}
except Exception as e:
logger.error("Failed to send email to %s: %s", to, str(e))
return {'success': False, 'error': str(e)}
def send_campaign_email(campaign_id, recipients, subject, html_body, text_body=None):
"""Send emails to all recipients in a campaign.
Args:
campaign_id: ID of the email campaign.
recipients: List of recipient email addresses.
subject: Email subject line.
html_body: HTML email body.
text_body: Plain text email body.
Returns:
dict with counts of sent/failed emails.
"""
results = {'sent': 0, 'failed': 0, 'errors': []}
for recipient in recipients:
result = send_email(
to=[recipient],
subject=subject,
html_body=html_body,
text_body=text_body,
)
if result['success']:
results['sent'] += 1
else:
results['failed'] += 1
results['errors'].append({'recipient': recipient, 'error': result.get('error')})
# Update delivery log status
try:
from app.models import EmailDeliveryLog, db
from app import mail
log_entry = EmailDeliveryLog.query.filter_by(
campaign_id=campaign_id,
recipient=recipient,
status='pending'
).first()
if log_entry:
if result['success']:
log_entry.status = 'sent'
else:
log_entry.status = 'failed'
log_entry.error_message = result.get('error', '')
db.session.commit()
except Exception as e:
logger.error("Failed to update delivery log for %s: %s", recipient, str(e))
return results