import httpx from typing import List, Optional, Dict, Any from datetime import datetime from ..core.database import get_db from ..models.models import EmailTemplate from sqlalchemy.orm import Session from ..core.config import settings from .bounce_service import bounce_service class EmailService: """Email service using SMTP2GO API with database-stored templates""" def __init__(self): self.api_key = settings.SMTP2GO_API_KEY self.api_url = settings.SMTP2GO_API_URL self.from_email = settings.EMAIL_FROM self.from_name = settings.EMAIL_FROM_NAME async def send_email( self, to_email: str, subject: str, html_body: str, text_body: Optional[str] = None, db: Optional[Session] = None ) -> dict: """ Send an email using SMTP2GO API Args: to_email: Recipient email address subject: Email subject html_body: HTML content of the email text_body: Plain text content (optional) db: Database session for bounce checking Returns: dict: API response Raises: ValueError: If email is bounced or invalid """ # Check if email is bounced before sending if db and bounce_service.is_email_bounced(to_email, db): raise ValueError(f"Email {to_email} is in bounce list and cannot receive emails") payload = { "to": [to_email], "sender": f"{self.from_name} <{self.from_email}>", "subject": subject, "html_body": html_body, } if text_body: payload["text_body"] = text_body headers = { "Content-Type": "application/json", "X-Smtp2go-Api-Key": self.api_key } async with httpx.AsyncClient() as client: response = await client.post(self.api_url, json=payload, headers=headers) return response.json() async def get_template(self, template_key: str, db: Session) -> Optional[EmailTemplate]: """Get email template from database""" return db.query(EmailTemplate).filter( EmailTemplate.template_key == template_key, EmailTemplate.is_active == True ).first() def render_template(self, template_body: str, variables: Dict[str, Any]) -> str: """Render template with variables""" result = template_body for key, value in variables.items(): result = result.replace(f"{{{key}}}", str(value)) return result async def send_templated_email( self, template_key: str, to_email: str, variables: Dict[str, Any], db: Session ) -> dict: """Send email using database template""" template = await self.get_template(template_key, db) if not template: raise ValueError(f"Email template '{template_key}' not found or inactive") # Render subject and bodies with variables subject = self.render_template(template.subject, variables) html_body = self.render_template(template.html_body, variables) text_body = None if template.text_body: text_body = self.render_template(template.text_body, variables) return await self.send_email(to_email, subject, html_body, text_body, db) async def send_welcome_email(self, to_email: str, first_name: str, db: Session) -> dict: """Send welcome email to new user""" variables = { "first_name": first_name, "app_name": settings.APP_NAME } return await self.send_templated_email("welcome", to_email, variables, db) async def send_payment_confirmation( self, to_email: str, first_name: str, amount: float, payment_method: str, membership_tier: str, db: Session ) -> dict: """Send payment confirmation email""" variables = { "first_name": first_name, "amount": f"£{amount:.2f}", "payment_method": payment_method, "membership_tier": membership_tier, "app_name": settings.APP_NAME } return await self.send_templated_email("payment_confirmation", to_email, variables, db) async def send_membership_activation_email( self, to_email: str, first_name: str, membership_tier: str, annual_fee: float, payment_amount: float, payment_method: str, renewal_date: str, db: Session ) -> dict: """Send membership activation email with payment details and renewal date""" variables = { "first_name": first_name, "membership_tier": membership_tier, "annual_fee": f"£{annual_fee:.2f}", "payment_amount": f"£{payment_amount:.2f}", "payment_method": payment_method, "renewal_date": renewal_date, "payment_date": datetime.now().strftime("%d %B %Y"), "app_name": settings.APP_NAME } return await self.send_templated_email("membership_activation", to_email, variables, db) async def send_membership_renewal_reminder( self, to_email: str, first_name: str, expiry_date: str, membership_tier: str, annual_fee: float, db: Session ) -> dict: """Send membership renewal reminder""" variables = { "first_name": first_name, "expiry_date": expiry_date, "membership_tier": membership_tier, "annual_fee": f"£{annual_fee:.2f}", "app_name": settings.APP_NAME } return await self.send_templated_email("renewal_reminder", to_email, variables, db) async def send_password_reset_email( self, to_email: str, first_name: str, reset_token: str, db: Session ) -> dict: """Send password reset email with reset link""" reset_url = f"{settings.FRONTEND_URL}/reset-password?token={reset_token}" variables = { "first_name": first_name, "reset_url": reset_url, "app_name": settings.APP_NAME } return await self.send_templated_email("password_reset", to_email, variables, db) def get_default_templates() -> List[Dict[str, Any]]: """Get default email templates for seeding the database""" return [ { "template_key": "welcome", "name": "Welcome Email", "subject": "Welcome to {app_name}!", "html_body": """
Hello {first_name},
Thank you for registering with us. Your account has been successfully created.
You can now:
If you have any questions, please don't hesitate to contact us.
Best regards,
{app_name}
Hello {first_name},
We have received your payment. Thank you!
Amount: {amount}
Payment Method: {payment_method}
Membership Tier: {membership_tier}
Your membership is now active. You can access all the benefits associated with your tier.
Best regards,
{app_name}
Hello {first_name},
Great news! Your membership has been successfully activated. You now have full access to all the benefits of your membership tier.
Membership Tier: {membership_tier}
Annual Fee: {annual_fee}
Next Renewal Date: {renewal_date}
Amount Paid: {payment_amount}
Payment Method: {payment_method}
Payment Date: {payment_date}
Your membership will automatically renew on {renewal_date} unless you choose to cancel it. You can manage your membership settings in your account dashboard.
If you have any questions about your membership or need assistance, please don't hesitate to contact us.
Welcome to the {app_name} community!
Best regards,
{app_name} Team
Hello {first_name},
This is a friendly reminder that your {membership_tier} membership will expire on {expiry_date}.
To continue enjoying your membership benefits, please renew your membership.
Membership Tier: {membership_tier}
Annual Fee: {annual_fee}
Expires: {expiry_date}
Please log in to your account to renew your membership.
Best regards,
{app_name}
Hello {first_name},
You have requested to reset your password for your {app_name} account.
Please click the button below to reset your password:
If the button doesn't work, you can copy and paste this link into your browser:
{reset_url}
This link will expire in 1 hour.
If you didn't request this password reset, please ignore this email. Your password will remain unchanged.
For security reasons, please don't share this email with anyone.
Best regards,
{app_name}