Email template management
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
from fastapi import APIRouter
|
||||
from . import auth, users, tiers, memberships, payments, email
|
||||
from . import auth, users, tiers, memberships, payments, email, email_templates
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
@@ -9,3 +9,4 @@ api_router.include_router(tiers.router, prefix="/tiers", tags=["membership-tiers
|
||||
api_router.include_router(memberships.router, prefix="/memberships", tags=["memberships"])
|
||||
api_router.include_router(payments.router, prefix="/payments", tags=["payments"])
|
||||
api_router.include_router(email.router, prefix="/email", tags=["email"])
|
||||
api_router.include_router(email_templates.router, prefix="/email-templates", tags=["email-templates"])
|
||||
|
||||
@@ -52,7 +52,8 @@ async def register(
|
||||
try:
|
||||
await email_service.send_welcome_email(
|
||||
to_email=db_user.email,
|
||||
first_name=db_user.first_name
|
||||
first_name=db_user.first_name,
|
||||
db=db
|
||||
)
|
||||
except Exception as e:
|
||||
# Log error but don't fail registration
|
||||
@@ -171,7 +172,8 @@ async def forgot_password(
|
||||
await email_service.send_password_reset_email(
|
||||
to_email=user.email,
|
||||
first_name=user.first_name,
|
||||
reset_token=reset_token
|
||||
reset_token=reset_token,
|
||||
db=db
|
||||
)
|
||||
except Exception as e:
|
||||
# Log error but don't fail the request
|
||||
|
||||
98
backend/app/api/v1/email_templates.py
Normal file
98
backend/app/api/v1/email_templates.py
Normal file
@@ -0,0 +1,98 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from ...core.database import get_db
|
||||
from ...models.models import EmailTemplate
|
||||
from ...schemas import (
|
||||
EmailTemplateCreate, EmailTemplateUpdate, EmailTemplateResponse, MessageResponse
|
||||
)
|
||||
from ...api.dependencies import get_super_admin_user
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/", response_model=List[EmailTemplateResponse])
|
||||
async def list_email_templates(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user = Depends(get_super_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""List all email templates (super admin only)"""
|
||||
templates = db.query(EmailTemplate).offset(skip).limit(limit).all()
|
||||
return templates
|
||||
|
||||
|
||||
@router.get("/{template_key}", response_model=EmailTemplateResponse)
|
||||
async def get_email_template(
|
||||
template_key: str,
|
||||
current_user = Depends(get_super_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get email template by key (super admin only)"""
|
||||
template = db.query(EmailTemplate).filter(EmailTemplate.template_key == template_key).first()
|
||||
|
||||
if not template:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Email template not found"
|
||||
)
|
||||
|
||||
return template
|
||||
|
||||
|
||||
@router.put("/{template_key}", response_model=EmailTemplateResponse)
|
||||
async def update_email_template(
|
||||
template_key: str,
|
||||
template_update: EmailTemplateUpdate,
|
||||
current_user = Depends(get_super_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Update email template (super admin only)"""
|
||||
template = db.query(EmailTemplate).filter(EmailTemplate.template_key == template_key).first()
|
||||
|
||||
if not template:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Email template not found"
|
||||
)
|
||||
|
||||
update_data = template_update.model_dump(exclude_unset=True)
|
||||
|
||||
for field, value in update_data.items():
|
||||
setattr(template, field, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(template)
|
||||
|
||||
return template
|
||||
|
||||
|
||||
@router.post("/seed-defaults", response_model=MessageResponse)
|
||||
async def seed_default_templates(
|
||||
current_user = Depends(get_super_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Seed database with default email templates (super admin only)"""
|
||||
from ...services.email_service import get_default_templates
|
||||
|
||||
default_templates = get_default_templates()
|
||||
created_count = 0
|
||||
|
||||
for template_data in default_templates:
|
||||
# Check if template already exists
|
||||
existing = db.query(EmailTemplate).filter(
|
||||
EmailTemplate.template_key == template_data["template_key"]
|
||||
).first()
|
||||
|
||||
if not existing:
|
||||
template = EmailTemplate(**template_data)
|
||||
db.add(template)
|
||||
created_count += 1
|
||||
|
||||
if created_count > 0:
|
||||
db.commit()
|
||||
return {"message": f"Created {created_count} default email templates"}
|
||||
else:
|
||||
return {"message": "All default templates already exist"}
|
||||
@@ -145,7 +145,8 @@ async def update_membership(
|
||||
annual_fee=membership.tier.annual_fee,
|
||||
payment_amount=payment_amount,
|
||||
payment_method=payment_method,
|
||||
renewal_date=membership.end_date.strftime("%d %B %Y")
|
||||
renewal_date=membership.end_date.strftime("%d %B %Y"),
|
||||
db=db
|
||||
)
|
||||
except Exception as e:
|
||||
# Log error but don't fail the membership update
|
||||
|
||||
@@ -134,7 +134,8 @@ async def update_payment(
|
||||
annual_fee=payment.membership.tier.annual_fee,
|
||||
payment_amount=payment.amount,
|
||||
payment_method=payment.payment_method.value,
|
||||
renewal_date=payment.membership.end_date.strftime("%d %B %Y")
|
||||
renewal_date=payment.membership.end_date.strftime("%d %B %Y"),
|
||||
db=db
|
||||
)
|
||||
except Exception as e:
|
||||
# Log error but don't fail the payment update
|
||||
@@ -219,7 +220,8 @@ async def record_manual_payment(
|
||||
annual_fee=membership.tier.annual_fee,
|
||||
payment_amount=payment.amount,
|
||||
payment_method=payment.payment_method.value,
|
||||
renewal_date=membership.end_date.strftime("%d %B %Y")
|
||||
renewal_date=membership.end_date.strftime("%d %B %Y"),
|
||||
db=db
|
||||
)
|
||||
except Exception as e:
|
||||
# Log error but don't fail the payment creation
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from sqlalchemy.orm import Session
|
||||
from ..models.models import MembershipTier, User, UserRole
|
||||
from ..models.models import MembershipTier, User, UserRole, EmailTemplate
|
||||
from .security import get_password_hash
|
||||
from datetime import datetime
|
||||
|
||||
@@ -54,3 +54,19 @@ def init_default_data(db: Session):
|
||||
db.commit()
|
||||
print("✓ Created default admin user (admin@swanseaairport.org / admin123)")
|
||||
print(" ⚠️ Remember to change the admin password!")
|
||||
|
||||
# Check if email templates exist
|
||||
existing_templates = db.query(EmailTemplate).count()
|
||||
if existing_templates == 0:
|
||||
print("Creating default email templates...")
|
||||
from ..services.email_service import get_default_templates
|
||||
default_templates_data = get_default_templates()
|
||||
|
||||
default_templates = []
|
||||
for template_data in default_templates_data:
|
||||
template = EmailTemplate(**template_data)
|
||||
default_templates.append(template)
|
||||
|
||||
db.add_all(default_templates)
|
||||
db.commit()
|
||||
print(f"✓ Created {len(default_templates)} default email templates")
|
||||
|
||||
@@ -273,3 +273,18 @@ class PasswordResetToken(Base):
|
||||
|
||||
# Relationships
|
||||
user = relationship("User", backref="password_reset_tokens")
|
||||
|
||||
|
||||
class EmailTemplate(Base):
|
||||
__tablename__ = "email_templates"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
template_key = Column(String(100), unique=True, nullable=False, index=True)
|
||||
name = Column(String(255), nullable=False)
|
||||
subject = Column(String(255), nullable=False)
|
||||
html_body = Column(Text, nullable=False)
|
||||
text_body = Column(Text, nullable=True)
|
||||
variables = Column(Text, nullable=True) # JSON string of available variables
|
||||
is_active = Column(Boolean, default=True, nullable=False)
|
||||
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
|
||||
|
||||
@@ -23,6 +23,10 @@ from .schemas import (
|
||||
PaymentUpdate,
|
||||
PaymentResponse,
|
||||
MessageResponse,
|
||||
EmailTemplateBase,
|
||||
EmailTemplateCreate,
|
||||
EmailTemplateUpdate,
|
||||
EmailTemplateResponse,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
@@ -50,4 +54,8 @@ __all__ = [
|
||||
"PaymentUpdate",
|
||||
"PaymentResponse",
|
||||
"MessageResponse",
|
||||
"EmailTemplateBase",
|
||||
"EmailTemplateCreate",
|
||||
"EmailTemplateUpdate",
|
||||
"EmailTemplateResponse",
|
||||
]
|
||||
|
||||
@@ -166,3 +166,35 @@ class PaymentResponse(BaseModel):
|
||||
class MessageResponse(BaseModel):
|
||||
message: str
|
||||
detail: Optional[str] = None
|
||||
|
||||
|
||||
# Email Template Schemas
|
||||
class EmailTemplateBase(BaseModel):
|
||||
template_key: str
|
||||
name: str
|
||||
subject: str
|
||||
html_body: str
|
||||
text_body: Optional[str] = None
|
||||
variables: Optional[str] = None
|
||||
|
||||
|
||||
class EmailTemplateCreate(EmailTemplateBase):
|
||||
pass
|
||||
|
||||
|
||||
class EmailTemplateUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
subject: Optional[str] = None
|
||||
html_body: Optional[str] = None
|
||||
text_body: Optional[str] = None
|
||||
variables: Optional[str] = None
|
||||
is_active: Optional[bool] = None
|
||||
|
||||
|
||||
class EmailTemplateResponse(EmailTemplateBase):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
is_active: bool
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import httpx
|
||||
from typing import List, Optional
|
||||
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
|
||||
|
||||
|
||||
class EmailService:
|
||||
"""Email service using SMTP2GO API"""
|
||||
"""Email service using SMTP2GO API with database-stored templates"""
|
||||
|
||||
def __init__(self):
|
||||
self.api_key = settings.SMTP2GO_API_KEY
|
||||
@@ -51,96 +54,69 @@ class EmailService:
|
||||
response = await client.post(self.api_url, json=payload, headers=headers)
|
||||
return response.json()
|
||||
|
||||
async def send_welcome_email(self, to_email: str, first_name: str) -> dict:
|
||||
"""Send welcome email to new user"""
|
||||
subject = f"Welcome to {settings.APP_NAME}!"
|
||||
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)
|
||||
|
||||
html_body = f"""
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
<h2 style="color: #0066cc;">Welcome to {settings.APP_NAME}!</h2>
|
||||
<p>Hello {first_name},</p>
|
||||
<p>Thank you for registering with us. Your account has been successfully created.</p>
|
||||
<p>You can now:</p>
|
||||
<ul>
|
||||
<li>Browse membership tiers and select one that suits you</li>
|
||||
<li>View upcoming events and meetings</li>
|
||||
<li>Access your membership portal</li>
|
||||
</ul>
|
||||
<p>If you have any questions, please don't hesitate to contact us.</p>
|
||||
<p>Best regards,<br>
|
||||
<strong>{settings.APP_NAME}</strong></p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
if not template:
|
||||
raise ValueError(f"Email template '{template_key}' not found or inactive")
|
||||
|
||||
text_body = f"""
|
||||
Welcome to {settings.APP_NAME}!
|
||||
# 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
|
||||
|
||||
Hello {first_name},
|
||||
|
||||
Thank you for registering with us. Your account has been successfully created.
|
||||
|
||||
You can now:
|
||||
- Browse membership tiers and select one that suits you
|
||||
- View upcoming events and meetings
|
||||
- Access your membership portal
|
||||
|
||||
If you have any questions, please don't hesitate to contact us.
|
||||
|
||||
Best regards,
|
||||
{settings.APP_NAME}
|
||||
"""
|
||||
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)
|
||||
|
||||
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
|
||||
membership_tier: str,
|
||||
db: Session
|
||||
) -> dict:
|
||||
"""Send payment confirmation email"""
|
||||
subject = "Payment Confirmation"
|
||||
|
||||
html_body = f"""
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
<h2 style="color: #0066cc;">Payment Confirmed!</h2>
|
||||
<p>Hello {first_name},</p>
|
||||
<p>We have received your payment. Thank you!</p>
|
||||
<div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin: 20px 0;">
|
||||
<p style="margin: 5px 0;"><strong>Amount:</strong> £{amount:.2f}</p>
|
||||
<p style="margin: 5px 0;"><strong>Payment Method:</strong> {payment_method}</p>
|
||||
<p style="margin: 5px 0;"><strong>Membership Tier:</strong> {membership_tier}</p>
|
||||
</div>
|
||||
<p>Your membership is now active. You can access all the benefits associated with your tier.</p>
|
||||
<p>Best regards,<br>
|
||||
<strong>{settings.APP_NAME}</strong></p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
text_body = f"""
|
||||
Payment Confirmed!
|
||||
|
||||
Hello {first_name},
|
||||
|
||||
We have received your payment. Thank you!
|
||||
|
||||
Amount: £{amount:.2f}
|
||||
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,
|
||||
{settings.APP_NAME}
|
||||
"""
|
||||
|
||||
return await self.send_email(to_email, subject, html_body, text_body)
|
||||
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,
|
||||
@@ -150,74 +126,21 @@ class EmailService:
|
||||
annual_fee: float,
|
||||
payment_amount: float,
|
||||
payment_method: str,
|
||||
renewal_date: str
|
||||
renewal_date: str,
|
||||
db: Session
|
||||
) -> dict:
|
||||
"""Send membership activation email with payment details and renewal date"""
|
||||
subject = f"Your {settings.APP_NAME} Membership is Now Active!"
|
||||
|
||||
html_body = f"""
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
<h2 style="color: #28a745;">Welcome to {settings.APP_NAME}!</h2>
|
||||
<p>Hello {first_name},</p>
|
||||
<p>Great news! Your membership has been successfully activated. You now have full access to all the benefits of your membership tier.</p>
|
||||
|
||||
<div style="background-color: #f8f9fa; padding: 20px; border-radius: 8px; margin: 20px 0; border-left: 4px solid #28a745;">
|
||||
<h3 style="margin-top: 0; color: #28a745;">Membership Details</h3>
|
||||
<p style="margin: 8px 0;"><strong>Membership Tier:</strong> {membership_tier}</p>
|
||||
<p style="margin: 8px 0;"><strong>Annual Fee:</strong> £{annual_fee:.2f}</p>
|
||||
<p style="margin: 8px 0;"><strong>Next Renewal Date:</strong> {renewal_date}</p>
|
||||
</div>
|
||||
|
||||
<div style="background-color: #e9ecef; padding: 20px; border-radius: 8px; margin: 20px 0;">
|
||||
<h3 style="margin-top: 0; color: #495057;">Payment Information</h3>
|
||||
<p style="margin: 8px 0;"><strong>Amount Paid:</strong> £{payment_amount:.2f}</p>
|
||||
<p style="margin: 8px 0;"><strong>Payment Method:</strong> {payment_method}</p>
|
||||
<p style="margin: 8px 0;"><strong>Payment Date:</strong> {datetime.now().strftime('%d %B %Y')}</p>
|
||||
</div>
|
||||
|
||||
<p>Your membership will automatically renew on <strong>{renewal_date}</strong> unless you choose to cancel it. You can manage your membership settings in your account dashboard.</p>
|
||||
|
||||
<p>If you have any questions about your membership or need assistance, please don't hesitate to contact us.</p>
|
||||
|
||||
<p>Welcome to the {settings.APP_NAME} community!</p>
|
||||
|
||||
<p>Best regards,<br>
|
||||
<strong>{settings.APP_NAME} Team</strong></p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
text_body = f"""
|
||||
Welcome to {settings.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 DETAILS
|
||||
------------------
|
||||
Membership Tier: {membership_tier}
|
||||
Annual Fee: £{annual_fee:.2f}
|
||||
Next Renewal Date: {renewal_date}
|
||||
|
||||
PAYMENT INFORMATION
|
||||
-------------------
|
||||
Amount Paid: £{payment_amount:.2f}
|
||||
Payment Method: {payment_method}
|
||||
Payment Date: {datetime.now().strftime('%d %B %Y')}
|
||||
|
||||
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 {settings.APP_NAME} community!
|
||||
|
||||
Best regards,
|
||||
{settings.APP_NAME} Team
|
||||
"""
|
||||
|
||||
return await self.send_email(to_email, subject, html_body, text_body)
|
||||
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,
|
||||
@@ -225,103 +148,277 @@ class EmailService:
|
||||
first_name: str,
|
||||
expiry_date: str,
|
||||
membership_tier: str,
|
||||
annual_fee: float
|
||||
annual_fee: float,
|
||||
db: Session
|
||||
) -> dict:
|
||||
"""Send membership renewal reminder"""
|
||||
subject = "Membership Renewal Reminder"
|
||||
|
||||
html_body = f"""
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
<h2 style="color: #0066cc;">Membership Renewal Reminder</h2>
|
||||
<p>Hello {first_name},</p>
|
||||
<p>This is a friendly reminder that your <strong>{membership_tier}</strong> membership will expire on <strong>{expiry_date}</strong>.</p>
|
||||
<p>To continue enjoying your membership benefits, please renew your membership.</p>
|
||||
<div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin: 20px 0;">
|
||||
<p style="margin: 5px 0;"><strong>Membership Tier:</strong> {membership_tier}</p>
|
||||
<p style="margin: 5px 0;"><strong>Annual Fee:</strong> £{annual_fee:.2f}</p>
|
||||
<p style="margin: 5px 0;"><strong>Expires:</strong> {expiry_date}</p>
|
||||
</div>
|
||||
<p>Please log in to your account to renew your membership.</p>
|
||||
<p>Best regards,<br>
|
||||
<strong>{settings.APP_NAME}</strong></p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
text_body = f"""
|
||||
Membership Renewal Reminder
|
||||
|
||||
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:.2f}
|
||||
Expires: {expiry_date}
|
||||
|
||||
Please log in to your account to renew your membership.
|
||||
|
||||
Best regards,
|
||||
{settings.APP_NAME}
|
||||
"""
|
||||
|
||||
return await self.send_email(to_email, subject, html_body, text_body)
|
||||
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
|
||||
reset_token: str,
|
||||
db: Session
|
||||
) -> dict:
|
||||
"""Send password reset email with reset link"""
|
||||
subject = f"Password Reset - {settings.APP_NAME}"
|
||||
|
||||
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": """
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
<h2 style="color: #0066cc;">Welcome to {app_name}!</h2>
|
||||
<p>Hello {first_name},</p>
|
||||
<p>Thank you for registering with us. Your account has been successfully created.</p>
|
||||
<p>You can now:</p>
|
||||
<ul>
|
||||
<li>Browse membership tiers and select one that suits you</li>
|
||||
<li>View upcoming events and meetings</li>
|
||||
<li>Access your membership portal</li>
|
||||
</ul>
|
||||
<p>If you have any questions, please don't hesitate to contact us.</p>
|
||||
<p>Best regards,<br>
|
||||
<strong>{app_name}</strong></p>
|
||||
</body>
|
||||
</html>
|
||||
""".strip(),
|
||||
"text_body": """
|
||||
Welcome to {app_name}!
|
||||
|
||||
Hello {first_name},
|
||||
|
||||
Thank you for registering with us. Your account has been successfully created.
|
||||
|
||||
You can now:
|
||||
- Browse membership tiers and select one that suits you
|
||||
- View upcoming events and meetings
|
||||
- Access your membership portal
|
||||
|
||||
If you have any questions, please don't hesitate to contact us.
|
||||
|
||||
Best regards,
|
||||
{app_name}
|
||||
""".strip(),
|
||||
"variables": '["first_name", "app_name"]',
|
||||
"is_active": True
|
||||
},
|
||||
{
|
||||
"template_key": "payment_confirmation",
|
||||
"name": "Payment Confirmation",
|
||||
"subject": "Payment Confirmation",
|
||||
"html_body": """
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
<h2 style="color: #0066cc;">Payment Confirmed!</h2>
|
||||
<p>Hello {first_name},</p>
|
||||
<p>We have received your payment. Thank you!</p>
|
||||
<div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin: 20px 0;">
|
||||
<p style="margin: 5px 0;"><strong>Amount:</strong> {amount}</p>
|
||||
<p style="margin: 5px 0;"><strong>Payment Method:</strong> {payment_method}</p>
|
||||
<p style="margin: 5px 0;"><strong>Membership Tier:</strong> {membership_tier}</p>
|
||||
</div>
|
||||
<p>Your membership is now active. You can access all the benefits associated with your tier.</p>
|
||||
<p>Best regards,<br>
|
||||
<strong>{app_name}</strong></p>
|
||||
</body>
|
||||
</html>
|
||||
""".strip(),
|
||||
"text_body": """
|
||||
Payment Confirmed!
|
||||
|
||||
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}
|
||||
""".strip(),
|
||||
"variables": '["first_name", "amount", "payment_method", "membership_tier", "app_name"]',
|
||||
"is_active": True
|
||||
},
|
||||
{
|
||||
"template_key": "membership_activation",
|
||||
"name": "Membership Activation",
|
||||
"subject": "Your {app_name} Membership is Now Active!",
|
||||
"html_body": """
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
<h2 style="color: #28a745;">Welcome to {app_name}!</h2>
|
||||
<p>Hello {first_name},</p>
|
||||
<p>Great news! Your membership has been successfully activated. You now have full access to all the benefits of your membership tier.</p>
|
||||
|
||||
html_body = f"""
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
<h2 style="color: #0066cc;">Password Reset Request</h2>
|
||||
<p>Hello {first_name},</p>
|
||||
<p>You have requested to reset your password for your {settings.APP_NAME} account.</p>
|
||||
<p>Please click the button below to reset your password:</p>
|
||||
<div style="text-align: center; margin: 30px 0;">
|
||||
<a href="{reset_url}" style="background-color: #0066cc; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; display: inline-block;">Reset Password</a>
|
||||
</div>
|
||||
<p>If the button doesn't work, you can copy and paste this link into your browser:</p>
|
||||
<p style="word-break: break-all; background-color: #f5f5f5; padding: 10px; border-radius: 3px;">{reset_url}</p>
|
||||
<p><strong>This link will expire in 1 hour.</strong></p>
|
||||
<p>If you didn't request this password reset, please ignore this email. Your password will remain unchanged.</p>
|
||||
<p>For security reasons, please don't share this email with anyone.</p>
|
||||
<p>Best regards,<br>
|
||||
<strong>{settings.APP_NAME}</strong></p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
<div style="background-color: #f8f9fa; padding: 20px; border-radius: 8px; margin: 20px 0; border-left: 4px solid #28a745;">
|
||||
<h3 style="margin-top: 0; color: #28a745;">Membership Details</h3>
|
||||
<p style="margin: 8px 0;"><strong>Membership Tier:</strong> {membership_tier}</p>
|
||||
<p style="margin: 8px 0;"><strong>Annual Fee:</strong> {annual_fee}</p>
|
||||
<p style="margin: 8px 0;"><strong>Next Renewal Date:</strong> {renewal_date}</p>
|
||||
</div>
|
||||
|
||||
text_body = f"""
|
||||
Password Reset Request
|
||||
<div style="background-color: #e9ecef; padding: 20px; border-radius: 8px; margin: 20px 0;">
|
||||
<h3 style="margin-top: 0; color: #495057;">Payment Information</h3>
|
||||
<p style="margin: 8px 0;"><strong>Amount Paid:</strong> {payment_amount}</p>
|
||||
<p style="margin: 8px 0;"><strong>Payment Method:</strong> {payment_method}</p>
|
||||
<p style="margin: 8px 0;"><strong>Payment Date:</strong> {payment_date}</p>
|
||||
</div>
|
||||
|
||||
Hello {first_name},
|
||||
<p>Your membership will automatically renew on <strong>{renewal_date}</strong> unless you choose to cancel it. You can manage your membership settings in your account dashboard.</p>
|
||||
|
||||
You have requested to reset your password for your {settings.APP_NAME} account.
|
||||
<p>If you have any questions about your membership or need assistance, please don't hesitate to contact us.</p>
|
||||
|
||||
Please use this link to reset your password: {reset_url}
|
||||
<p>Welcome to the {app_name} community!</p>
|
||||
|
||||
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,
|
||||
{settings.APP_NAME}
|
||||
"""
|
||||
|
||||
return await self.send_email(to_email, subject, html_body, text_body)
|
||||
<p>Best regards,<br>
|
||||
<strong>{app_name} Team</strong></p>
|
||||
</body>
|
||||
</html>
|
||||
""".strip(),
|
||||
"text_body": """
|
||||
Welcome to {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 DETAILS
|
||||
------------------
|
||||
Membership Tier: {membership_tier}
|
||||
Annual Fee: {annual_fee}
|
||||
Next Renewal Date: {renewal_date}
|
||||
|
||||
PAYMENT INFORMATION
|
||||
-------------------
|
||||
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
|
||||
""".strip(),
|
||||
"variables": '["first_name", "membership_tier", "annual_fee", "payment_amount", "payment_method", "renewal_date", "payment_date", "app_name"]',
|
||||
"is_active": True
|
||||
},
|
||||
{
|
||||
"template_key": "renewal_reminder",
|
||||
"name": "Membership Renewal Reminder",
|
||||
"subject": "Membership Renewal Reminder",
|
||||
"html_body": """
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
<h2 style="color: #0066cc;">Membership Renewal Reminder</h2>
|
||||
<p>Hello {first_name},</p>
|
||||
<p>This is a friendly reminder that your <strong>{membership_tier}</strong> membership will expire on <strong>{expiry_date}</strong>.</p>
|
||||
<p>To continue enjoying your membership benefits, please renew your membership.</p>
|
||||
<div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin: 20px 0;">
|
||||
<p style="margin: 5px 0;"><strong>Membership Tier:</strong> {membership_tier}</p>
|
||||
<p style="margin: 5px 0;"><strong>Annual Fee:</strong> {annual_fee}</p>
|
||||
<p style="margin: 5px 0;"><strong>Expires:</strong> {expiry_date}</p>
|
||||
</div>
|
||||
<p>Please log in to your account to renew your membership.</p>
|
||||
<p>Best regards,<br>
|
||||
<strong>{app_name}</strong></p>
|
||||
</body>
|
||||
</html>
|
||||
""".strip(),
|
||||
"text_body": """
|
||||
Membership Renewal Reminder
|
||||
|
||||
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}
|
||||
""".strip(),
|
||||
"variables": '["first_name", "expiry_date", "membership_tier", "annual_fee", "app_name"]',
|
||||
"is_active": True
|
||||
},
|
||||
{
|
||||
"template_key": "password_reset",
|
||||
"name": "Password Reset",
|
||||
"subject": "Password Reset - {app_name}",
|
||||
"html_body": """
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
<h2 style="color: #0066cc;">Password Reset Request</h2>
|
||||
<p>Hello {first_name},</p>
|
||||
<p>You have requested to reset your password for your {app_name} account.</p>
|
||||
<p>Please click the button below to reset your password:</p>
|
||||
<div style="text-align: center; margin: 30px 0;">
|
||||
<a href="{reset_url}" style="background-color: #0066cc; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; display: inline-block;">Reset Password</a>
|
||||
</div>
|
||||
<p>If the button doesn't work, you can copy and paste this link into your browser:</p>
|
||||
<p style="word-break: break-all; background-color: #f5f5f5; padding: 10px; border-radius: 3px;">{reset_url}</p>
|
||||
<p><strong>This link will expire in 1 hour.</strong></p>
|
||||
<p>If you didn't request this password reset, please ignore this email. Your password will remain unchanged.</p>
|
||||
<p>For security reasons, please don't share this email with anyone.</p>
|
||||
<p>Best regards,<br>
|
||||
<strong>{app_name}</strong></p>
|
||||
</body>
|
||||
</html>
|
||||
""".strip(),
|
||||
"text_body": """
|
||||
Password Reset Request
|
||||
|
||||
Hello {first_name},
|
||||
|
||||
You have requested to reset your password for your {app_name} account.
|
||||
|
||||
Please use this link to reset your password: {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}
|
||||
""".strip(),
|
||||
"variables": '["first_name", "reset_url", "app_name"]',
|
||||
"is_active": True
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
# Create a singleton instance
|
||||
|
||||
Reference in New Issue
Block a user