from datetime import datetime from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText import logging import os import smtplib logger = logging.getLogger(__name__) class EmailService: def __init__(self): # Load SMTP configuration from environment self.smtp_host = os.environ.get('SMTP_HOST', '') self.smtp_port = int(os.environ.get('SMTP_PORT', 587)) self.smtp_user = os.environ.get('SMTP_USER', '') self.smtp_password = os.environ.get('SMTP_PASSWORD', '') self.smtp_from = os.environ.get('SMTP_FROM', '') self.smtp_from_name = os.environ.get('SMTP_FROM_NAME', 'AnchorHost') # TLS configuration - IMPORTANT! use_tls_str = os.environ.get('SMTP_USE_TLS', '').lower() # Default to True for port 587, False for other ports if use_tls_str: self.use_tls = use_tls_str in ('true', '1', 'yes', 'on') else: # Default: Use TLS on port 587, not on other ports self.use_tls = (self.smtp_port == 587) # Base URL for verification links self.base_url = os.environ.get('BASE_URL', 'http://localhost:8080') # Validate configuration if not self.smtp_password: logger.warning("⚠️ SMTP_PASSWORD not set - email sending will fail!") logger.info(f"Email Service initialized") logger.info(f"SMTP Host: {self.smtp_host}:{self.smtp_port}") logger.info(f"SMTP User: {self.smtp_user}") logger.info(f"From: {self.smtp_from_name} <{self.smtp_from}>") logger.info(f"Use TLS: {self.use_tls}") def send_email(self, to_email: str, subject: str, html_body: str, text_body: str = None) -> bool: """ Send an email using SMTP Args: to_email: Recipient email address subject: Email subject html_body: HTML content of the email text_body: Plain text fallback (optional) Returns: bool: True if email was sent successfully """ if not all((self.smtp_host, self.smtp_user, self.smtp_password, self.smtp_from)): logger.warning("Email not sent because SMTP settings are incomplete") return False try: # Create message msg = MIMEMultipart('alternative') msg['Subject'] = subject msg['From'] = f"{self.smtp_from_name} <{self.smtp_from}>" msg['To'] = to_email # Add text part (fallback) if text_body: part1 = MIMEText(text_body, 'plain') msg.attach(part1) # Add HTML part part2 = MIMEText(html_body, 'html') msg.attach(part2) # Connect and send logger.info(f"Connecting to SMTP server: {self.smtp_host}:{self.smtp_port}") with smtplib.SMTP(self.smtp_host, self.smtp_port) as server: server.ehlo() # Check if TLS should be used (ONLY if explicitly enabled) # Default behavior: Use TLS on port 587 UNLESS SMTP_USE_TLS=false use_tls = self.smtp_port == 587 and self.use_tls if use_tls: logger.info("Starting TLS...") try: server.starttls() server.ehlo() except smtplib.SMTPNotSupportedError: logger.warning("TLS not supported by server, continuing without encryption") else: logger.info("Skipping TLS (not enabled or not port 587)") # Login logger.info(f"Logging in as: {self.smtp_user}") server.login(self.smtp_user, self.smtp_password) # Send email logger.info(f"Sending email to: {to_email}") server.send_message(msg) logger.info(f"✅ Email sent successfully to {to_email}") return True except smtplib.SMTPAuthenticationError as e: logger.error(f"❌ SMTP Authentication failed: {e}") return False except smtplib.SMTPException as e: logger.error(f"❌ SMTP error: {e}") return False except Exception as e: logger.error(f"❌ Error sending email: {e}", exc_info=True) return False def send_verification_email(self, to_email: str, username: str, token: str) -> bool: """ Send email verification email Args: to_email: User's email address username: Username token: Verification token Returns: bool: True if email was sent successfully """ verification_url = f"{self.base_url}/verify-email?token={token}" subject = "Verify your AnchorHost account" # HTML email template html_body = f"""
Hello {username},
Your monthly fee for server {server_name} has been processed.
Server: {server_name}
Amount: {amount} credits ({amount_euro:.2f} EUR)
Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
New Balance: {new_balance} credits
Your server will remain active as long as you maintain sufficient credits.
You can add more credits at any time from your dashboard.
If you have any questions, please contact our support team.
Hello {username},
Your server {server_name} has been suspended due to insufficient credits.
Required: {required_amount} credits ({required_euro:.2f} EUR)
Current Balance: {current_balance} credits
Missing: {required_amount - current_balance} credits
To reactivate your server:
Note: Your server data is preserved for 30 days. After this period, it may be permanently deleted.
If you believe this is an error, please contact our support team immediately.