Email template management

This commit is contained in:
James Pattinson
2025-11-10 16:07:22 +00:00
parent 43b13ef52d
commit 7fd237c28b
17 changed files with 1421 additions and 259 deletions

View File

@@ -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"])

View File

@@ -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

View 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"}

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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)

View File

@@ -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",
]

View File

@@ -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

View File

@@ -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