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""" Verify Your Email

AnchorHost

Welcome to AnchorHost, {username}!

Thank you for registering with AnchorHost. To complete your registration and start using our services, please verify your email address.

Click the button below to verify your email:

Verify Email Address

If the button doesn't work, copy and paste this link into your browser:

{verification_url}

Important: This verification link will expire in 24 hours.

If you didn't create an account with AnchorHost, please ignore this email.

© 2024 AnchorHost. All rights reserved.

AnchorHost.eu | Support

""" # Plain text fallback text_body = f""" Welcome to AnchorHost, {username}! Thank you for registering. Please verify your email address by clicking the link below: {verification_url} This link will expire in 24 hours. If you didn't create an account with AnchorHost, please ignore this email. --- AnchorHost.eu """ return self.send_email(to_email, subject, html_body, text_body) def send_password_reset_email(self, to_email: str, username: str, token: str) -> bool: """ Send password reset email Args: to_email: User's email address username: Username token: Reset token Returns: bool: True if email was sent successfully """ reset_url = f"{self.base_url}/reset-password?token={token}" subject = "Reset your AnchorHost password" html_body = f""" Reset Your Password

Password Reset

Reset your password, {username}

We received a request to reset your password. Click the button below to create a new password:

Reset Password

Or copy this link: {reset_url}

This link expires in 1 hour. If you didn't request this, please ignore this email.

© 2024 AnchorHost. All rights reserved.

""" text_body = f""" Reset your password, {username} We received a request to reset your password. Click the link below: {reset_url} This link expires in 1 hour. If you didn't request this, ignore this email. --- AnchorHost.eu """ return self.send_email(to_email, subject, html_body, text_body) def send_welcome_email(self, to_email: str, username: str) -> bool: """Send welcome email after successful verification""" subject = "Welcome to AnchorHost!" html_body = f"""

🎉 Welcome to AnchorHost!

You're all set, {username}!

Your email has been verified and your account is now active. You can start deploying servers right away!

Go to Dashboard

© 2024 AnchorHost. All rights reserved.

""" return self.send_email(to_email, subject, html_body) def send_monthly_fee_email(self, to_email: str, username: str, server_name: str, amount: int, new_balance: int) -> bool: """Send monthly fee confirmation email""" try: subject = f"AnchorHost - Monthly Fee for {server_name}" # Convert credits to the displayed euro amount. amount_euro = amount / 100 body = f"""

Monthly Server Fee

Hello {username},

Your monthly fee for server {server_name} has been processed.

Payment Details:

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.

""" return self.send_email(to_email, subject, body) except Exception as e: logger.error(f"Error sending monthly fee email: {e}") return False def send_server_suspended_email(self, to_email: str, username: str, server_name: str, required_amount: int, current_balance: int) -> bool: """Send server suspension email due to insufficient credits""" try: subject = f"AnchorHost - Server {server_name} Suspended" required_euro = required_amount / 100 body = f"""

Server Suspended

Hello {username},

Your server {server_name} has been suspended due to insufficient credits.

Important Notice:

Required: {required_amount} credits ({required_euro:.2f} EUR)

Current Balance: {current_balance} credits

Missing: {required_amount - current_balance} credits

To reactivate your server:

  1. Log in to your AnchorHost account
  2. Add credits to your account (minimum {required_amount - current_balance} credits)
  3. Start your server from the dashboard
Add Credits Now

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.

""" return self.send_email(to_email, subject, body) except Exception as e: logger.error(f"Error sending suspension email: {e}") return False # Initialize global email service instance email_service = None def init_email_service(): """Initialize the email service""" global email_service try: email_service = EmailService() logger.info("✅ Email service initialized") return email_service except Exception as e: logger.error(f"❌ Failed to initialize email service: {e}") return None def get_email_service(): """Get the email service instance""" global email_service if email_service is None: return init_email_service() return email_service