Source code for coalition.endorsements.email_service

"""
Email service for endorsement verification and notifications
"""

import logging
from smtplib import SMTPException

from django.conf import settings
from django.core.mail import send_mail
from django.template.exceptions import TemplateDoesNotExist
from django.template.loader import render_to_string
from django.utils import timezone

from .models import Endorsement

[docs] logger = logging.getLogger(__name__)
[docs] class EndorsementEmailService: """Service for sending endorsement-related emails""" @staticmethod
[docs] def send_verification_email(endorsement: Endorsement) -> bool: """ Send email verification to stakeholder Returns True if email was sent successfully """ try: # Generate verification URL verification_url = ( f"{settings.SITE_URL}/verify-endorsement/" f"{endorsement.verification_token}/" ) # Email context context = { "endorsement": endorsement, "stakeholder": endorsement.stakeholder, "campaign": endorsement.campaign, "verification_url": verification_url, "site_url": settings.SITE_URL, "organization_name": settings.ORGANIZATION_NAME, } # Render email content subject = f"Please verify your endorsement for {endorsement.campaign.title}" # HTML email content html_message = render_to_string( "emails/endorsement_verification.html", context, ) # Plain text fallback plain_message = render_to_string( "emails/endorsement_verification.txt", context, ) # Send email success = send_mail( subject=subject, message=plain_message, from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[endorsement.stakeholder.email], html_message=html_message, fail_silently=False, ) if success: # Update verification sent timestamp endorsement.verification_sent_at = timezone.now() endorsement.save(update_fields=["verification_sent_at"]) logger.info( f"Verification email sent to {endorsement.stakeholder.email} " f"for endorsement {endorsement.id}", ) return True else: logger.error( f"Failed to send verification email for endorsement " f"{endorsement.id}", ) return False except TemplateDoesNotExist as e: logger.error( f"Email template not found for endorsement " f"{endorsement.id}: {str(e)}", ) return False except SMTPException as e: logger.error( f"SMTP error sending verification email for endorsement " f"{endorsement.id}: {str(e)}", ) return False except (AttributeError, KeyError) as e: logger.error( f"Configuration error sending verification email for endorsement " f"{endorsement.id}: {str(e)}", ) return False
@staticmethod
[docs] def send_admin_notification(endorsement: Endorsement) -> bool: """ Send notification to admins about new endorsement requiring review Returns True if email was sent successfully """ try: # Get admin emails from settings or use a default admin_emails = getattr(settings, "ADMIN_NOTIFICATION_EMAILS", "") if isinstance(admin_emails, str): admin_emails = [ email.strip() for email in admin_emails.split(",") if email.strip() ] if not admin_emails and hasattr(settings, "ADMINS"): admin_emails = [email for name, email in settings.ADMINS] if not admin_emails: logger.warning( "No admin emails configured for endorsement notifications", ) return False # Email context context = { "endorsement": endorsement, "stakeholder": endorsement.stakeholder, "campaign": endorsement.campaign, "admin_url": ( f"{settings.API_URL}/admin/endorsements/endorsement/" f"{endorsement.id}/change/" ), "organization_name": settings.ORGANIZATION_NAME, } # Render email content subject = f"New endorsement requires review: {endorsement.campaign.title}" # HTML email content html_message = render_to_string( "emails/admin_endorsement_notification.html", context, ) # Plain text fallback plain_message = render_to_string( "emails/admin_endorsement_notification.txt", context, ) # Send email to all admins success = send_mail( subject=subject, message=plain_message, from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=admin_emails, html_message=html_message, fail_silently=False, ) if success: logger.info(f"Admin notification sent for endorsement {endorsement.id}") return True else: logger.error( f"Failed to send admin notification for endorsement " f"{endorsement.id}", ) return False except TemplateDoesNotExist as e: logger.error( f"Email template not found for admin notification " f"{endorsement.id}: {str(e)}", ) return False except SMTPException as e: logger.error( f"SMTP error sending admin notification for endorsement " f"{endorsement.id}: {str(e)}", ) return False except (AttributeError, KeyError) as e: logger.error( f"Configuration error sending admin notification for endorsement " f"{endorsement.id}: {str(e)}", ) return False
@staticmethod
[docs] def send_confirmation_email(endorsement: Endorsement) -> bool: """ Send confirmation email to stakeholder when endorsement is approved Returns True if email was sent successfully """ try: # Email context context = { "endorsement": endorsement, "stakeholder": endorsement.stakeholder, "campaign": endorsement.campaign, "campaign_url": ( f"{settings.SITE_URL}/campaigns/{endorsement.campaign.id}/" ), "organization_name": settings.ORGANIZATION_NAME, } # Render email content subject = ( f"Your endorsement for {endorsement.campaign.title} has been approved" ) # HTML email content html_message = render_to_string("emails/endorsement_approved.html", context) # Plain text fallback plain_message = render_to_string("emails/endorsement_approved.txt", context) # Send email success = send_mail( subject=subject, message=plain_message, from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[endorsement.stakeholder.email], html_message=html_message, fail_silently=False, ) if success: logger.info( f"Approval confirmation sent to {endorsement.stakeholder.email} " f"for endorsement {endorsement.id}", ) return True else: logger.error( f"Failed to send approval confirmation for endorsement " f"{endorsement.id}", ) return False except TemplateDoesNotExist as e: logger.error( f"Email template not found for approval confirmation " f"{endorsement.id}: {str(e)}", ) return False except SMTPException as e: logger.error( f"SMTP error sending approval confirmation for endorsement " f"{endorsement.id}: {str(e)}", ) return False except (AttributeError, KeyError) as e: logger.error( f"Configuration error sending approval confirmation for endorsement " f"{endorsement.id}: {str(e)}", ) return False