update
This commit is contained in:
BIN
Backend/src/utils/__pycache__/email_templates.cpython-312.pyc
Normal file
BIN
Backend/src/utils/__pycache__/email_templates.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
261
Backend/src/utils/email_templates.py
Normal file
261
Backend/src/utils/email_templates.py
Normal file
@@ -0,0 +1,261 @@
|
||||
"""
|
||||
Email templates for various notifications
|
||||
"""
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def get_base_template(content: str, title: str = "Hotel Booking") -> str:
|
||||
"""Base HTML email template"""
|
||||
return f"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #f4f4f4;">
|
||||
<table role="presentation" style="width: 100%; border-collapse: collapse;">
|
||||
<tr>
|
||||
<td style="padding: 20px 0; text-align: center; background-color: #4F46E5;">
|
||||
<h1 style="color: #ffffff; margin: 0;">Hotel Booking</h1>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 40px 20px; background-color: #ffffff;">
|
||||
<table role="presentation" style="width: 100%; max-width: 600px; margin: 0 auto;">
|
||||
<tr>
|
||||
<td>
|
||||
{content}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 20px; text-align: center; background-color: #f4f4f4; color: #666666; font-size: 12px;">
|
||||
<p style="margin: 0;">This is an automated email. Please do not reply.</p>
|
||||
<p style="margin: 5px 0 0 0;">© {datetime.now().year} Hotel Booking. All rights reserved.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
def welcome_email_template(name: str, email: str, client_url: str) -> str:
|
||||
"""Welcome email template for new registrations"""
|
||||
content = f"""
|
||||
<h2 style="color: #4F46E5; margin-top: 0;">Welcome {name}!</h2>
|
||||
<p>Thank you for registering an account at <strong>Hotel Booking</strong>.</p>
|
||||
<p>Your account has been successfully created with email: <strong>{email}</strong></p>
|
||||
<div style="background-color: #F3F4F6; padding: 20px; border-radius: 8px; margin: 20px 0;">
|
||||
<p style="margin: 0;"><strong>You can:</strong></p>
|
||||
<ul style="margin-top: 10px;">
|
||||
<li>Search and book hotel rooms</li>
|
||||
<li>Manage your bookings</li>
|
||||
<li>Update your personal information</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p style="text-align: center; margin-top: 30px;">
|
||||
<a href="{client_url}/login" style="background-color: #4F46E5; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">
|
||||
Login Now
|
||||
</a>
|
||||
</p>
|
||||
"""
|
||||
return get_base_template(content, "Welcome to Hotel Booking")
|
||||
|
||||
|
||||
def password_reset_email_template(reset_url: str) -> str:
|
||||
"""Password reset email template"""
|
||||
content = f"""
|
||||
<h2 style="color: #4F46E5; margin-top: 0;">Password Reset Request</h2>
|
||||
<p>You (or someone) has requested to reset your password.</p>
|
||||
<p>Click the link below to reset your password. This link will expire in 1 hour:</p>
|
||||
<p style="text-align: center; margin: 30px 0;">
|
||||
<a href="{reset_url}" style="background-color: #4F46E5; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">
|
||||
Reset Password
|
||||
</a>
|
||||
</p>
|
||||
<p style="color: #666666; font-size: 14px;">If you did not request this, please ignore this email.</p>
|
||||
"""
|
||||
return get_base_template(content, "Password Reset")
|
||||
|
||||
|
||||
def password_changed_email_template(email: str) -> str:
|
||||
"""Password changed confirmation email template"""
|
||||
content = f"""
|
||||
<h2 style="color: #4F46E5; margin-top: 0;">Password Changed Successfully</h2>
|
||||
<p>The password for account <strong>{email}</strong> has been changed successfully.</p>
|
||||
<p>If you did not make this change, please contact our support team immediately.</p>
|
||||
"""
|
||||
return get_base_template(content, "Password Changed")
|
||||
|
||||
|
||||
def booking_confirmation_email_template(
|
||||
booking_number: str,
|
||||
guest_name: str,
|
||||
room_number: str,
|
||||
room_type: str,
|
||||
check_in: str,
|
||||
check_out: str,
|
||||
num_guests: int,
|
||||
total_price: float,
|
||||
requires_deposit: bool,
|
||||
deposit_amount: Optional[float] = None,
|
||||
client_url: str = "http://localhost:5173"
|
||||
) -> str:
|
||||
"""Booking confirmation email template"""
|
||||
deposit_info = ""
|
||||
if requires_deposit and deposit_amount:
|
||||
deposit_info = f"""
|
||||
<div style="background-color: #FEF3C7; border-left: 4px solid #F59E0B; padding: 15px; margin: 20px 0; border-radius: 4px;">
|
||||
<p style="margin: 0; font-weight: bold; color: #92400E;">⚠️ Deposit Required</p>
|
||||
<p style="margin: 5px 0 0 0; color: #78350F;">Please pay a deposit of <strong>€{deposit_amount:.2f}</strong> to confirm your booking.</p>
|
||||
<p style="margin: 5px 0 0 0; color: #78350F;">Your booking will be confirmed once the deposit is received.</p>
|
||||
</div>
|
||||
"""
|
||||
|
||||
content = f"""
|
||||
<h2 style="color: #4F46E5; margin-top: 0;">Booking Confirmation</h2>
|
||||
<p>Dear {guest_name},</p>
|
||||
<p>Thank you for your booking! We have received your reservation request.</p>
|
||||
|
||||
<div style="background-color: #F3F4F6; padding: 20px; border-radius: 8px; margin: 20px 0;">
|
||||
<h3 style="margin-top: 0; color: #1F2937;">Booking Details</h3>
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<tr>
|
||||
<td style="padding: 8px 0; color: #6B7280; width: 40%;">Booking Number:</td>
|
||||
<td style="padding: 8px 0; font-weight: bold; color: #1F2937;">{booking_number}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0; color: #6B7280;">Room:</td>
|
||||
<td style="padding: 8px 0; color: #1F2937;">{room_type} - Room {room_number}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0; color: #6B7280;">Check-in:</td>
|
||||
<td style="padding: 8px 0; color: #1F2937;">{check_in}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0; color: #6B7280;">Check-out:</td>
|
||||
<td style="padding: 8px 0; color: #1F2937;">{check_out}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0; color: #6B7280;">Guests:</td>
|
||||
<td style="padding: 8px 0; color: #1F2937;">{num_guests}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0; color: #6B7280;">Total Price:</td>
|
||||
<td style="padding: 8px 0; font-weight: bold; color: #1F2937;">€{total_price:.2f}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{deposit_info}
|
||||
|
||||
<p style="text-align: center; margin-top: 30px;">
|
||||
<a href="{client_url}/bookings/{booking_number}" style="background-color: #4F46E5; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">
|
||||
View Booking Details
|
||||
</a>
|
||||
</p>
|
||||
"""
|
||||
return get_base_template(content, "Booking Confirmation")
|
||||
|
||||
|
||||
def payment_confirmation_email_template(
|
||||
booking_number: str,
|
||||
guest_name: str,
|
||||
amount: float,
|
||||
payment_method: str,
|
||||
transaction_id: Optional[str] = None,
|
||||
client_url: str = "http://localhost:5173"
|
||||
) -> str:
|
||||
"""Payment confirmation email template"""
|
||||
transaction_info = ""
|
||||
if transaction_id:
|
||||
transaction_info = f"""
|
||||
<tr>
|
||||
<td style="padding: 8px 0; color: #6B7280;">Transaction ID:</td>
|
||||
<td style="padding: 8px 0; color: #1F2937; font-family: monospace;">{transaction_id}</td>
|
||||
</tr>
|
||||
"""
|
||||
|
||||
content = f"""
|
||||
<h2 style="color: #10B981; margin-top: 0;">Payment Received</h2>
|
||||
<p>Dear {guest_name},</p>
|
||||
<p>We have successfully received your payment for booking <strong>{booking_number}</strong>.</p>
|
||||
|
||||
<div style="background-color: #ECFDF5; border-left: 4px solid #10B981; padding: 20px; border-radius: 8px; margin: 20px 0;">
|
||||
<h3 style="margin-top: 0; color: #065F46;">Payment Details</h3>
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<tr>
|
||||
<td style="padding: 8px 0; color: #6B7280; width: 40%;">Booking Number:</td>
|
||||
<td style="padding: 8px 0; font-weight: bold; color: #1F2937;">{booking_number}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0; color: #6B7280;">Amount:</td>
|
||||
<td style="padding: 8px 0; font-weight: bold; color: #065F46; font-size: 18px;">€{amount:.2f}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0; color: #6B7280;">Payment Method:</td>
|
||||
<td style="padding: 8px 0; color: #1F2937;">{payment_method}</td>
|
||||
</tr>
|
||||
{transaction_info}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p>Your booking is now confirmed. We look forward to hosting you!</p>
|
||||
|
||||
<p style="text-align: center; margin-top: 30px;">
|
||||
<a href="{client_url}/bookings/{booking_number}" style="background-color: #10B981; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">
|
||||
View Booking
|
||||
</a>
|
||||
</p>
|
||||
"""
|
||||
return get_base_template(content, "Payment Confirmation")
|
||||
|
||||
|
||||
def booking_status_changed_email_template(
|
||||
booking_number: str,
|
||||
guest_name: str,
|
||||
status: str,
|
||||
client_url: str = "http://localhost:5173"
|
||||
) -> str:
|
||||
"""Booking status change email template"""
|
||||
status_colors = {
|
||||
"confirmed": ("#10B981", "Confirmed"),
|
||||
"cancelled": ("#EF4444", "Cancelled"),
|
||||
"checked_in": ("#3B82F6", "Checked In"),
|
||||
"checked_out": ("#8B5CF6", "Checked Out"),
|
||||
}
|
||||
|
||||
color, status_text = status_colors.get(status.lower(), ("#6B7280", status.title()))
|
||||
|
||||
content = f"""
|
||||
<h2 style="color: {color}; margin-top: 0;">Booking Status Updated</h2>
|
||||
<p>Dear {guest_name},</p>
|
||||
<p>Your booking status has been updated.</p>
|
||||
|
||||
<div style="background-color: #F3F4F6; padding: 20px; border-radius: 8px; margin: 20px 0;">
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<tr>
|
||||
<td style="padding: 8px 0; color: #6B7280; width: 40%;">Booking Number:</td>
|
||||
<td style="padding: 8px 0; font-weight: bold; color: #1F2937;">{booking_number}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0; color: #6B7280;">New Status:</td>
|
||||
<td style="padding: 8px 0; font-weight: bold; color: {color}; font-size: 18px;">{status_text}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p style="text-align: center; margin-top: 30px;">
|
||||
<a href="{client_url}/bookings/{booking_number}" style="background-color: {color}; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">
|
||||
View Booking
|
||||
</a>
|
||||
</p>
|
||||
"""
|
||||
return get_base_template(content, f"Booking {status_text}")
|
||||
|
||||
@@ -2,47 +2,96 @@ import aiosmtplib
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
import os
|
||||
import logging
|
||||
from ..config.settings import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def send_email(to: str, subject: str, html: str = None, text: str = None):
|
||||
"""
|
||||
Send email using SMTP
|
||||
Requires MAIL_HOST, MAIL_USER and MAIL_PASS to be set in env.
|
||||
Uses settings from config/settings.py with fallback to environment variables
|
||||
"""
|
||||
# Require SMTP credentials to be present
|
||||
mail_host = os.getenv("MAIL_HOST")
|
||||
mail_user = os.getenv("MAIL_USER")
|
||||
mail_pass = os.getenv("MAIL_PASS")
|
||||
try:
|
||||
# Get SMTP settings from settings.py, fallback to env vars
|
||||
mail_host = settings.SMTP_HOST or os.getenv("MAIL_HOST")
|
||||
mail_user = settings.SMTP_USER or os.getenv("MAIL_USER")
|
||||
mail_pass = settings.SMTP_PASSWORD or os.getenv("MAIL_PASS")
|
||||
mail_port = settings.SMTP_PORT or int(os.getenv("MAIL_PORT", "587"))
|
||||
mail_secure = os.getenv("MAIL_SECURE", "false").lower() == "true"
|
||||
client_url = settings.CLIENT_URL or os.getenv("CLIENT_URL", "http://localhost:5173")
|
||||
|
||||
# Get from address - prefer settings, then env, then generate from client_url
|
||||
from_address = settings.SMTP_FROM_EMAIL or os.getenv("MAIL_FROM")
|
||||
if not from_address:
|
||||
# Generate from client_url if not set
|
||||
domain = client_url.replace('https://', '').replace('http://', '').split('/')[0]
|
||||
from_address = f"no-reply@{domain}"
|
||||
|
||||
# Use from name if available
|
||||
from_name = settings.SMTP_FROM_NAME or "Hotel Booking"
|
||||
from_header = f"{from_name} <{from_address}>"
|
||||
|
||||
if not (mail_host and mail_user and mail_pass):
|
||||
raise ValueError(
|
||||
"SMTP mailer not configured. Set MAIL_HOST, MAIL_USER and MAIL_PASS in env."
|
||||
if not (mail_host and mail_user and mail_pass):
|
||||
error_msg = "SMTP mailer not configured. Set SMTP_HOST, SMTP_USER and SMTP_PASSWORD in .env file."
|
||||
logger.error(error_msg)
|
||||
raise ValueError(error_msg)
|
||||
|
||||
# Create message
|
||||
message = MIMEMultipart("alternative")
|
||||
message["From"] = from_header
|
||||
message["To"] = to
|
||||
message["Subject"] = subject
|
||||
|
||||
if text:
|
||||
message.attach(MIMEText(text, "plain"))
|
||||
if html:
|
||||
message.attach(MIMEText(html, "html"))
|
||||
|
||||
# If no content provided, add a default text
|
||||
if not text and not html:
|
||||
message.attach(MIMEText("", "plain"))
|
||||
|
||||
# Determine TLS/SSL settings
|
||||
# For port 587: use STARTTLS (use_tls=False, start_tls=True)
|
||||
# For port 465: use SSL/TLS (use_tls=True, start_tls=False)
|
||||
# For port 25: plain (usually not used for authenticated sending)
|
||||
if mail_port == 465 or mail_secure:
|
||||
# SSL/TLS connection (port 465)
|
||||
use_tls = True
|
||||
start_tls = False
|
||||
elif mail_port == 587:
|
||||
# STARTTLS connection (port 587)
|
||||
use_tls = False
|
||||
start_tls = True
|
||||
else:
|
||||
# Plain connection (port 25 or other)
|
||||
use_tls = False
|
||||
start_tls = False
|
||||
|
||||
logger.info(f"Attempting to send email to {to} via {mail_host}:{mail_port} (use_tls: {use_tls}, start_tls: {start_tls})")
|
||||
|
||||
# Send email using SMTP client
|
||||
smtp_client = aiosmtplib.SMTP(
|
||||
hostname=mail_host,
|
||||
port=mail_port,
|
||||
use_tls=use_tls,
|
||||
start_tls=start_tls,
|
||||
username=mail_user,
|
||||
password=mail_pass,
|
||||
)
|
||||
|
||||
mail_port = int(os.getenv("MAIL_PORT", "587"))
|
||||
mail_secure = os.getenv("MAIL_SECURE", "false").lower() == "true"
|
||||
client_url = os.getenv("CLIENT_URL", "example.com")
|
||||
from_address = os.getenv("MAIL_FROM", f"no-reply@{client_url.replace('https://', '').replace('http://', '')}")
|
||||
|
||||
# Create message
|
||||
message = MIMEMultipart("alternative")
|
||||
message["From"] = from_address
|
||||
message["To"] = to
|
||||
message["Subject"] = subject
|
||||
|
||||
if text:
|
||||
message.attach(MIMEText(text, "plain"))
|
||||
if html:
|
||||
message.attach(MIMEText(html, "html"))
|
||||
|
||||
# Send email
|
||||
await aiosmtplib.send(
|
||||
message,
|
||||
hostname=mail_host,
|
||||
port=mail_port,
|
||||
use_tls=not mail_secure and mail_port == 587,
|
||||
start_tls=not mail_secure and mail_port == 587,
|
||||
username=mail_user,
|
||||
password=mail_pass,
|
||||
)
|
||||
|
||||
try:
|
||||
await smtp_client.connect()
|
||||
# Authentication happens automatically if username/password are provided in constructor
|
||||
await smtp_client.send_message(message)
|
||||
logger.info(f"Email sent successfully to {to}")
|
||||
finally:
|
||||
await smtp_client.quit()
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Failed to send email to {to}: {type(e).__name__}: {str(e)}"
|
||||
logger.error(error_msg, exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
Reference in New Issue
Block a user