514 lines
22 KiB
Python
514 lines
22 KiB
Python
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"""
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Verify Your Email</title>
|
|
</head>
|
|
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #0f0f0f;">
|
|
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #0f0f0f; padding: 40px 20px;">
|
|
<tr>
|
|
<td align="center">
|
|
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #1a1a1a; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);">
|
|
<!-- Header -->
|
|
<tr>
|
|
<td style="background: linear-gradient(135deg, #5a7ca7 0%, #4a6385 100%); padding: 40px 30px; text-align: center;">
|
|
<h1 style="margin: 0; color: #ffffff; font-size: 28px; font-weight: bold;">
|
|
AnchorHost
|
|
</h1>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Body -->
|
|
<tr>
|
|
<td style="padding: 40px 30px;">
|
|
<h2 style="margin: 0 0 20px 0; color: #cbcbcd; font-size: 24px;">
|
|
Welcome to AnchorHost, {username}!
|
|
</h2>
|
|
|
|
<p style="margin: 0 0 20px 0; color: #8a8a8a; font-size: 16px; line-height: 1.6;">
|
|
Thank you for registering with AnchorHost. To complete your registration and start using our services, please verify your email address.
|
|
</p>
|
|
|
|
<p style="margin: 0 0 30px 0; color: #8a8a8a; font-size: 16px; line-height: 1.6;">
|
|
Click the button below to verify your email:
|
|
</p>
|
|
|
|
<!-- Verification Button -->
|
|
<table width="100%" cellpadding="0" cellspacing="0">
|
|
<tr>
|
|
<td align="center" style="padding: 20px 0;">
|
|
<a href="{verification_url}" style="display: inline-block; padding: 16px 40px; background: linear-gradient(135deg, #5a7ca7 0%, #4a6385 100%); color: #ffffff; text-decoration: none; font-size: 16px; font-weight: bold; border-radius: 8px; box-shadow: 0 4px 6px rgba(90, 124, 167, 0.3);">
|
|
Verify Email Address
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<p style="margin: 30px 0 20px 0; color: #8a8a8a; font-size: 14px; line-height: 1.6;">
|
|
If the button doesn't work, copy and paste this link into your browser:
|
|
</p>
|
|
|
|
<p style="margin: 0 0 30px 0; padding: 15px; background-color: #262626; border-radius: 6px; color: #5a7ca7; font-size: 13px; word-break: break-all; border-left: 3px solid #5a7ca7;">
|
|
{verification_url}
|
|
</p>
|
|
|
|
<p style="margin: 0 0 10px 0; color: #8a8a8a; font-size: 14px; line-height: 1.6;">
|
|
<strong style="color: #cbcbcd;">Important:</strong> This verification link will expire in 24 hours.
|
|
</p>
|
|
|
|
<p style="margin: 0; color: #8a8a8a; font-size: 14px; line-height: 1.6;">
|
|
If you didn't create an account with AnchorHost, please ignore this email.
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Footer -->
|
|
<tr>
|
|
<td style="background-color: #161616; padding: 30px; text-align: center; border-top: 1px solid #262626;">
|
|
<p style="margin: 0 0 10px 0; color: #8a8a8a; font-size: 12px;">
|
|
© 2024 AnchorHost. All rights reserved.
|
|
</p>
|
|
<p style="margin: 0; color: #8a8a8a; font-size: 12px;">
|
|
<a href="https://anchorhost.eu" style="color: #5a7ca7; text-decoration: none;">AnchorHost.eu</a> |
|
|
<a href="mailto:support@anchorhost.eu" style="color: #5a7ca7; text-decoration: none;">Support</a>
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
# 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"""
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Reset Your Password</title>
|
|
</head>
|
|
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #0f0f0f;">
|
|
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #0f0f0f; padding: 40px 20px;">
|
|
<tr>
|
|
<td align="center">
|
|
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #1a1a1a; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);">
|
|
<tr>
|
|
<td style="background: linear-gradient(135deg, #8b2d32 0%, #a83a40 100%); padding: 40px 30px; text-align: center;">
|
|
<h1 style="margin: 0; color: #ffffff; font-size: 28px; font-weight: bold;">
|
|
Password Reset
|
|
</h1>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td style="padding: 40px 30px;">
|
|
<h2 style="margin: 0 0 20px 0; color: #cbcbcd; font-size: 24px;">
|
|
Reset your password, {username}
|
|
</h2>
|
|
|
|
<p style="margin: 0 0 20px 0; color: #8a8a8a; font-size: 16px; line-height: 1.6;">
|
|
We received a request to reset your password. Click the button below to create a new password:
|
|
</p>
|
|
|
|
<table width="100%" cellpadding="0" cellspacing="0">
|
|
<tr>
|
|
<td align="center" style="padding: 20px 0;">
|
|
<a href="{reset_url}" style="display: inline-block; padding: 16px 40px; background: linear-gradient(135deg, #8b2d32 0%, #a83a40 100%); color: #ffffff; text-decoration: none; font-size: 16px; font-weight: bold; border-radius: 8px;">
|
|
Reset Password
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<p style="margin: 30px 0 20px 0; color: #8a8a8a; font-size: 14px; line-height: 1.6;">
|
|
Or copy this link: {reset_url}
|
|
</p>
|
|
|
|
<p style="margin: 0; color: #8a8a8a; font-size: 14px; line-height: 1.6;">
|
|
This link expires in 1 hour. If you didn't request this, please ignore this email.
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td style="background-color: #161616; padding: 30px; text-align: center; border-top: 1px solid #262626;">
|
|
<p style="margin: 0; color: #8a8a8a; font-size: 12px;">
|
|
© 2024 AnchorHost. All rights reserved.
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
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"""
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
</head>
|
|
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #0f0f0f;">
|
|
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #0f0f0f; padding: 40px 20px;">
|
|
<tr>
|
|
<td align="center">
|
|
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #1a1a1a; border-radius: 12px; overflow: hidden;">
|
|
<tr>
|
|
<td style="background: linear-gradient(135deg, #5a6b47 0%, #6b7d55 100%); padding: 40px 30px; text-align: center;">
|
|
<h1 style="margin: 0; color: #ffffff; font-size: 28px;">🎉 Welcome to AnchorHost!</h1>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td style="padding: 40px 30px;">
|
|
<h2 style="margin: 0 0 20px 0; color: #cbcbcd; font-size: 24px;">
|
|
You're all set, {username}!
|
|
</h2>
|
|
|
|
<p style="margin: 0 0 20px 0; color: #8a8a8a; font-size: 16px; line-height: 1.6;">
|
|
Your email has been verified and your account is now active. You can start deploying servers right away!
|
|
</p>
|
|
|
|
<table width="100%" cellpadding="0" cellspacing="0">
|
|
<tr>
|
|
<td align="center" style="padding: 20px 0;">
|
|
<a href="https://anchorhost.eu/dashboard" style="display: inline-block; padding: 16px 40px; background: linear-gradient(135deg, #5a6b47 0%, #6b7d55 100%); color: #ffffff; text-decoration: none; font-size: 16px; font-weight: bold; border-radius: 8px;">
|
|
Go to Dashboard
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td style="background-color: #161616; padding: 30px; text-align: center;">
|
|
<p style="margin: 0; color: #8a8a8a; font-size: 12px;">
|
|
© 2024 AnchorHost. All rights reserved.
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
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"""
|
|
<html>
|
|
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
|
<div style="max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #ddd; border-radius: 5px;">
|
|
<h2 style="color: #4a6fa5;">Monthly Server Fee</h2>
|
|
<p>Hello {username},</p>
|
|
<p>Your monthly fee for server <strong>{server_name}</strong> has been processed.</p>
|
|
|
|
<div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin: 20px 0;">
|
|
<h3 style="margin-top: 0;">Payment Details:</h3>
|
|
<p><strong>Server:</strong> {server_name}</p>
|
|
<p><strong>Amount:</strong> {amount} credits ({amount_euro:.2f} EUR)</p>
|
|
<p><strong>Date:</strong> {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
|
|
<p><strong>New Balance:</strong> {new_balance} credits</p>
|
|
</div>
|
|
|
|
<p>Your server will remain active as long as you maintain sufficient credits.</p>
|
|
<p>You can add more credits at any time from your dashboard.</p>
|
|
|
|
<hr style="border: none; border-top: 1px solid #eee; margin: 30px 0;">
|
|
|
|
<p style="font-size: 12px; color: #666;">
|
|
If you have any questions, please contact our support team.
|
|
</p>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
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"""
|
|
<html>
|
|
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
|
<div style="max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #ddd; border-radius: 5px;">
|
|
<h2 style="color: #d9534f;">Server Suspended</h2>
|
|
<p>Hello {username},</p>
|
|
<p>Your server <strong>{server_name}</strong> has been <strong>suspended</strong> due to insufficient credits.</p>
|
|
|
|
<div style="background-color: #f2dede; padding: 15px; border-radius: 5px; margin: 20px 0; border: 1px solid #ebccd1;">
|
|
<h3 style="margin-top: 0; color: #a94442;">Important Notice:</h3>
|
|
<p><strong>Required:</strong> {required_amount} credits ({required_euro:.2f} EUR)</p>
|
|
<p><strong>Current Balance:</strong> {current_balance} credits</p>
|
|
<p><strong>Missing:</strong> {required_amount - current_balance} credits</p>
|
|
</div>
|
|
|
|
<p>To reactivate your server:</p>
|
|
<ol>
|
|
<li>Log in to your AnchorHost account</li>
|
|
<li>Add credits to your account (minimum {required_amount - current_balance} credits)</li>
|
|
<li>Start your server from the dashboard</li>
|
|
</ol>
|
|
|
|
<div style="text-align: center; margin: 30px 0;">
|
|
<a href="https://your-domain.com/dashboard" style="background-color: #5a7ca7; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; font-weight: bold;">
|
|
Add Credits Now
|
|
</a>
|
|
</div>
|
|
|
|
<p><strong>Note:</strong> Your server data is preserved for 30 days. After this period, it may be permanently deleted.</p>
|
|
|
|
<hr style="border: none; border-top: 1px solid #eee; margin: 30px 0;">
|
|
|
|
<p style="font-size: 12px; color: #666;">
|
|
If you believe this is an error, please contact our support team immediately.
|
|
</p>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
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
|