02e95a4199
When a user accepts an org invite, send a notification email to the person who sent the invite with membership details (member name, email, org name, role) and an optional View Organization button. Added build_invite_accepted_html() template to email_templates.py, wired it into the accept_invite() handler, and added a test case.
613 lines
25 KiB
Python
613 lines
25 KiB
Python
"""HTML Email Templates for Secuird.
|
|
|
|
This module provides beautifully designed HTML email templates with
|
|
Secuird branding, responsive design, and consistent styling.
|
|
"""
|
|
import os
|
|
from typing import Optional
|
|
|
|
from flask import current_app
|
|
|
|
PRIMARY_COLOR = "#36b9a6"
|
|
PRIMARY_DARK = "#2d9a89"
|
|
TEXT_COLOR = "#1e293b"
|
|
MUTED_COLOR = "#64748b"
|
|
BORDER_COLOR = "#e2e8f0"
|
|
BACKGROUND_COLOR = "#f8fafc"
|
|
WHITE = "#ffffff"
|
|
DANGER_COLOR = "#dc2626"
|
|
WARNING_COLOR = "#f59e0b"
|
|
SUCCESS_COLOR = "#16a34a"
|
|
|
|
|
|
def get_logo_url() -> str:
|
|
"""Get the email logo URL from config or use default inline SVG."""
|
|
return current_app.config.get("EMAIL_BRAND_LOGO_URL", "")
|
|
|
|
|
|
def get_brand_name() -> str:
|
|
"""Get the brand name from config."""
|
|
return current_app.config.get("EMAIL_BRAND_NAME", "Secuird")
|
|
|
|
|
|
def get_support_email() -> str:
|
|
"""Get the support email from config."""
|
|
return current_app.config.get("EMAIL_SUPPORT_EMAIL", "support@secuird.tech")
|
|
|
|
|
|
def get_website_url() -> str:
|
|
"""Get the website URL from config."""
|
|
return current_app.config.get("EMAIL_WEBSITE_URL", "https://secuird.tech")
|
|
|
|
|
|
def get_app_url() -> str:
|
|
"""Get the app URL from config."""
|
|
return current_app.config.get("APP_URL", "https://secuird.tech")
|
|
|
|
|
|
def get_inline_logo() -> str:
|
|
"""Returns an inline SVG logo as a data URI for email embedding."""
|
|
return (
|
|
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" '
|
|
'width="40" height="40"><rect width="24" height="24" rx="4" fill="#36b9a6"/>'
|
|
'<path d="M4 4h3v16H4V4z" fill="#ffffff"/><path d="M17 4h3v16h-3V4z" fill="#ffffff"/>'
|
|
'<path d="M7 4h10v3H7V4z" fill="#ffffff" opacity="0.7"/>'
|
|
'<circle cx="12" cy="14" r="2" fill="#ffffff" opacity="0.5"/></svg>'
|
|
)
|
|
|
|
|
|
def get_base_html(
|
|
content: str,
|
|
subject: str,
|
|
preheader: Optional[str] = None,
|
|
) -> str:
|
|
"""Generate the base HTML email template.
|
|
|
|
Args:
|
|
content: The main content HTML
|
|
subject: Email subject (used for title and header)
|
|
preheader: Preview text shown in email clients
|
|
|
|
Returns:
|
|
Complete HTML email string
|
|
"""
|
|
logo = get_inline_logo()
|
|
brand_name = get_brand_name()
|
|
support_email = get_support_email()
|
|
website_url = get_website_url()
|
|
app_url = get_app_url()
|
|
current_year = __import__("datetime").datetime.now().year
|
|
|
|
return f'''<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
<title>{subject}</title>
|
|
<!--[if mso]>
|
|
<style type="text/css">
|
|
table {{ border-collapse: collapse; }}
|
|
.button {{ padding: 12px 24px !important; }}
|
|
</style>
|
|
<![endif]-->
|
|
</head>
|
|
<body style="margin: 0; padding: 0; background-color: {BACKGROUND_COLOR}; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;">
|
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background-color: {BACKGROUND_COLOR};">
|
|
<tr>
|
|
<td align="center" style="padding: 40px 20px;">
|
|
<!-- Email Container -->
|
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="max-width: 600px; background-color: {WHITE}; border-radius: 12px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);">
|
|
<!-- Header -->
|
|
<tr>
|
|
<td style="background: linear-gradient(135deg, {PRIMARY_COLOR}, {PRIMARY_DARK}); border-radius: 12px 12px 0 0; padding: 32px; text-align: center;">
|
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0">
|
|
<tr>
|
|
<td style="text-align: center;">
|
|
{logo}
|
|
<h1 style="margin: 16px 0 0 0; color: {WHITE}; font-size: 24px; font-weight: 600;">{brand_name}</h1>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Content -->
|
|
<tr>
|
|
<td style="padding: 40px 32px;">
|
|
{content}
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Divider -->
|
|
<tr>
|
|
<td style="padding: 0 32px;">
|
|
<hr style="border: none; border-top: 1px solid {BORDER_COLOR}; margin: 0;">
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Footer -->
|
|
<tr>
|
|
<td style="padding: 24px 32px; background-color: {BACKGROUND_COLOR}; border-radius: 0 0 12px 12px;">
|
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0">
|
|
<tr>
|
|
<td style="text-align: center; color: {MUTED_COLOR}; font-size: 13px;">
|
|
<p style="margin: 0 0 8px 0;">© {current_year} {brand_name}. All rights reserved.</p>
|
|
<p style="margin: 0;">
|
|
<a href="{website_url}" style="color: {PRIMARY_COLOR}; text-decoration: none;">Website</a>
|
|
•
|
|
<a href="mailto:{support_email}" style="color: {PRIMARY_COLOR}; text-decoration: none;">Support</a>
|
|
•
|
|
<a href="{app_url}" style="color: {PRIMARY_COLOR}; text-decoration: none;">App</a>
|
|
</p>
|
|
<p style="margin: 12px 0 0 0; font-size: 11px; color: #94a3b8;">
|
|
This email was sent because you have an account with {brand_name}.
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</body>
|
|
</html>'''
|
|
|
|
|
|
def get_action_button(link: str, text: str, color: str = PRIMARY_COLOR) -> str:
|
|
"""Generate an HTML action button.
|
|
|
|
Args:
|
|
link: The URL the button links to
|
|
text: Button text
|
|
color: Button background color
|
|
|
|
Returns:
|
|
HTML button string
|
|
"""
|
|
return f'''<table role="presentation" cellspacing="0" cellpadding="0" style="margin: 24px 0;">
|
|
<tr>
|
|
<td style="border-radius: 8px; background: linear-gradient(135deg, {color}, {color});">
|
|
<a href="{link}" style="display: inline-block; padding: 14px 32px; color: {WHITE}; text-decoration: none; font-weight: 600; font-size: 15px; border-radius: 8px;">{text}</a>
|
|
</td>
|
|
</tr>
|
|
</table>'''
|
|
|
|
|
|
def get_alert_box(text: str, alert_type: str = "info", icon: str = "") -> str:
|
|
"""Generate an alert/highlight box.
|
|
|
|
Args:
|
|
text: Alert text
|
|
alert_type: Type of alert (info, warning, danger, success)
|
|
icon: Optional icon emoji or HTML
|
|
|
|
Returns:
|
|
HTML alert box string
|
|
"""
|
|
colors = {
|
|
"info": (PRIMARY_COLOR, "#e0f2f1"),
|
|
"warning": (WARNING_COLOR, "#fef3c7"),
|
|
"danger": (DANGER_COLOR, "#fee2e2"),
|
|
"success": (SUCCESS_COLOR, "#dcfce7"),
|
|
}
|
|
border_color, bg_color = colors.get(alert_type, colors["info"])
|
|
|
|
return f'''<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="margin: 20px 0;">
|
|
<tr>
|
|
<td style="background-color: {bg_color}; border-left: 4px solid {border_color}; padding: 16px 20px; border-radius: 0 8px 8px 0;">
|
|
<p style="margin: 0; color: {TEXT_COLOR}; font-size: 14px;">{icon} {text}</p>
|
|
</td>
|
|
</tr>
|
|
</table>'''
|
|
|
|
|
|
def get_detail_row(label: str, value: str) -> str:
|
|
"""Generate a detail row for email content.
|
|
|
|
Args:
|
|
label: Field label
|
|
value: Field value
|
|
|
|
Returns:
|
|
HTML row string
|
|
"""
|
|
return f'''<tr>
|
|
<td style="padding: 8px 0; border-bottom: 1px solid {BORDER_COLOR};">
|
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0">
|
|
<tr>
|
|
<td width="40%" style="color: {MUTED_COLOR}; font-size: 13px;">{label}</td>
|
|
<td width="60%" style="color: {TEXT_COLOR}; font-size: 14px; font-weight: 500;">{value}</td>
|
|
</tr>
|
|
</table>
|
|
</td>
|
|
</tr>'''
|
|
|
|
|
|
# =============================================================================
|
|
# EMAIL TEMPLATES
|
|
# =============================================================================
|
|
|
|
|
|
def build_email_verification_html(
|
|
user_name: str,
|
|
verify_link: str,
|
|
expiry_hours: int = 24,
|
|
) -> str:
|
|
"""Build email verification email (welcome email).
|
|
|
|
Args:
|
|
user_name: Recipient's name or email
|
|
verify_link: Verification link URL
|
|
expiry_hours: Hours until link expires
|
|
|
|
Returns:
|
|
HTML email string
|
|
"""
|
|
content = f'''
|
|
<h2 style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 20px; font-weight: 600;">Welcome to Secuird!</h2>
|
|
<p style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 15px; line-height: 1.6;">
|
|
Hi <strong>{user_name}</strong>,
|
|
</p>
|
|
<p style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 15px; line-height: 1.6;">
|
|
Thank you for registering with Secuird. Please verify your email address by clicking the button below:
|
|
</p>
|
|
{get_action_button(verify_link, "Verify Email Address")}
|
|
<p style="margin: 20px 0; color: {MUTED_COLOR}; font-size: 13px;">
|
|
This link will expire in <strong>{expiry_hours} hours</strong>. If you didn't create an account, you can safely ignore this email.
|
|
</p>
|
|
{get_alert_box("For security reasons, please don't forward this email to anyone.", "warning", "⚠️")}
|
|
'''
|
|
return get_base_html(content, "Verify your Secuird email address", "Please verify your email address to activate your account")
|
|
|
|
|
|
def build_password_reset_html(
|
|
user_name: str,
|
|
reset_link: str,
|
|
expiry_hours: int = 2,
|
|
) -> str:
|
|
"""Build password reset email.
|
|
|
|
Args:
|
|
user_name: Recipient's name or email
|
|
reset_link: Password reset link URL
|
|
expiry_hours: Hours until link expires
|
|
|
|
Returns:
|
|
HTML email string
|
|
"""
|
|
content = f'''
|
|
<h2 style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 20px; font-weight: 600;">Reset Your Password</h2>
|
|
<p style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 15px; line-height: 1.6;">
|
|
Hi <strong>{user_name}</strong>,
|
|
</p>
|
|
<p style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 15px; line-height: 1.6;">
|
|
We received a request to reset your password. Click the button below to create a new one:
|
|
</p>
|
|
{get_action_button(reset_link, "Reset Password", WARNING_COLOR)}
|
|
<p style="margin: 20px 0; color: {MUTED_COLOR}; font-size: 13px;">
|
|
This link will expire in <strong>{expiry_hours} hours</strong>.
|
|
</p>
|
|
{get_alert_box("If you didn't request a password reset, your account is secure. You can safely ignore this email.", "info", "🔒")}
|
|
'''
|
|
return get_base_html(content, "Reset your Secuird password", "Click the button to reset your password")
|
|
|
|
|
|
def build_account_activation_html(
|
|
user_name: str,
|
|
activation_link: str,
|
|
) -> str:
|
|
"""Build account activation email.
|
|
|
|
Args:
|
|
user_name: Recipient's name or email
|
|
activation_link: Account activation link URL
|
|
|
|
Returns:
|
|
HTML email string
|
|
"""
|
|
content = f'''
|
|
<h2 style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 20px; font-weight: 600;">Activate Your Account</h2>
|
|
<p style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 15px; line-height: 1.6;">
|
|
Hi <strong>{user_name}</strong>,
|
|
</p>
|
|
<p style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 15px; line-height: 1.6;">
|
|
Your account has been created but is not yet activated. Click the button below to activate it:
|
|
</p>
|
|
{get_action_button(activation_link, "Activate Account", SUCCESS_COLOR)}
|
|
{get_alert_box("If you didn't create an account, you can safely ignore this email.", "warning", "⚠️")}
|
|
'''
|
|
return get_base_html(content, "Activate your Secuird account", "Activate your account to get started")
|
|
|
|
|
|
def build_mfa_deadline_reminder_html(
|
|
user_name: str,
|
|
org_name: str,
|
|
days_remaining: int,
|
|
deadline_date: str,
|
|
mfa_methods: str,
|
|
setup_link: str,
|
|
) -> str:
|
|
"""Build MFA deadline reminder email.
|
|
|
|
Args:
|
|
user_name: Recipient's name or email
|
|
org_name: Organization name
|
|
days_remaining: Days until MFA deadline
|
|
deadline_date: Formatted deadline date
|
|
mfa_methods: Required MFA methods
|
|
setup_link: Link to set up MFA
|
|
|
|
Returns:
|
|
HTML email string
|
|
"""
|
|
urgency = "immediate action" if days_remaining <= 3 else "attention required"
|
|
|
|
content = f'''
|
|
<h2 style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 20px; font-weight: 600;">MFA Enrollment {urgency.title()}</h2>
|
|
<p style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 15px; line-height: 1.6;">
|
|
Dear <strong>{user_name}</strong>,
|
|
</p>
|
|
{get_alert_box(f"<strong>Important:</strong> You have <strong>{days_remaining} days</strong> to set up multi-factor authentication for your account with {org_name}.", "warning", "⏰")}
|
|
<p style="margin: 20px 0; color: {TEXT_COLOR}; font-size: 15px; line-height: 1.6;">
|
|
To maintain access to your account, please complete the following:
|
|
</p>
|
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="margin: 20px 0; background-color: {BACKGROUND_COLOR}; border-radius: 8px;">
|
|
<tr>
|
|
<td style="padding: 20px;">
|
|
<h3 style="margin: 0 0 12px 0; color: {TEXT_COLOR}; font-size: 14px; font-weight: 600;">Required MFA Methods:</h3>
|
|
<p style="margin: 0; color: {MUTED_COLOR}; font-size: 14px;">{mfa_methods}</p>
|
|
<h3 style="margin: 16px 0 12px 0; color: {TEXT_COLOR}; font-size: 14px; font-weight: 600;">Deadline:</h3>
|
|
<p style="margin: 0; color: {DANGER_COLOR}; font-size: 14px; font-weight: 600;">{deadline_date}</p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
{get_action_button(setup_link, "Set Up MFA Now", PRIMARY_COLOR)}
|
|
<p style="margin: 20px 0; color: {MUTED_COLOR}; font-size: 13px;">
|
|
If you do not set up MFA by the deadline, your account access will be restricted.
|
|
</p>
|
|
<p style="margin: 0; color: {MUTED_COLOR}; font-size: 13px;">
|
|
If you have questions, please contact your organization administrator.
|
|
</p>
|
|
'''
|
|
subject = f"Action Required: MFA enrollment deadline in {days_remaining} days"
|
|
return get_base_html(content, subject, f"MFA enrollment required for {org_name} - {days_remaining} days remaining")
|
|
|
|
|
|
def build_mfa_suspension_html(
|
|
user_name: str,
|
|
org_name: str,
|
|
mfa_methods: str,
|
|
setup_link: str,
|
|
) -> str:
|
|
"""Build MFA suspension notification email.
|
|
|
|
Args:
|
|
user_name: Recipient's name or email
|
|
org_name: Organization name
|
|
mfa_methods: Required MFA methods
|
|
setup_link: Link to set up MFA
|
|
|
|
Returns:
|
|
HTML email string
|
|
"""
|
|
content = f'''
|
|
<h2 style="margin: 0 0 20px 0; color: {DANGER_COLOR}; font-size: 20px; font-weight: 600;">Account Access Restricted</h2>
|
|
<p style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 15px; line-height: 1.6;">
|
|
Dear <strong>{user_name}</strong>,
|
|
</p>
|
|
{get_alert_box("<strong>Your account has been suspended</strong> because you did not set up multi-factor authentication within the required timeframe.", "danger", "🚫")}
|
|
<p style="margin: 20px 0; color: {TEXT_COLOR}; font-size: 15px; line-height: 1.6;">
|
|
To restore access to your account with <strong>{org_name}</strong>, please complete the following:
|
|
</p>
|
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="margin: 20px 0; background-color: {BACKGROUND_COLOR}; border-radius: 8px;">
|
|
<tr>
|
|
<td style="padding: 20px;">
|
|
<h3 style="margin: 0 0 12px 0; color: {TEXT_COLOR}; font-size: 14px; font-weight: 600;">Required MFA Methods:</h3>
|
|
<p style="margin: 0; color: {MUTED_COLOR}; font-size: 14px;">{mfa_methods}</p>
|
|
<h3 style="margin: 16px 0 12px 0; color: {TEXT_COLOR}; font-size: 14px; font-weight: 600;">How to Restore Access:</h3>
|
|
<ol style="margin: 0; padding-left: 20px; color: {TEXT_COLOR}; font-size: 14px; line-height: 1.8;">
|
|
<li>Log in to your account (you will see a compliance enrollment screen)</li>
|
|
<li>Follow the prompts to set up an authenticator app or passkey</li>
|
|
<li>Once MFA is configured, your access will be restored</li>
|
|
</ol>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
{get_action_button(setup_link, "Set Up MFA Now", DANGER_COLOR)}
|
|
<p style="margin: 20px 0; color: {MUTED_COLOR}; font-size: 13px;">
|
|
Need help? Contact your organization administrator.
|
|
</p>
|
|
'''
|
|
return get_base_html(content, "Account Access Restricted - MFA Enrollment Required", "Your account has been suspended due to missing MFA")
|
|
|
|
|
|
def build_org_invite_html(
|
|
inviter_name: str,
|
|
org_name: str,
|
|
invite_link: str,
|
|
role: str,
|
|
expiry_days: int = 7,
|
|
) -> str:
|
|
"""Build organization invite email.
|
|
|
|
Args:
|
|
inviter_name: Name of person who sent the invite
|
|
org_name: Organization name
|
|
invite_link: Invitation acceptance link
|
|
role: Role the invitee will have
|
|
expiry_days: Days until invite expires
|
|
|
|
Returns:
|
|
HTML email string
|
|
"""
|
|
content = f'''
|
|
<h2 style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 20px; font-weight: 600;">You're Invited to Join {org_name}</h2>
|
|
<p style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 15px; line-height: 1.6;">
|
|
You've been invited by <strong>{inviter_name}</strong> to join <strong>{org_name}</strong> on Secuird.
|
|
</p>
|
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="margin: 20px 0; background-color: {BACKGROUND_COLOR}; border-radius: 8px;">
|
|
<tr>
|
|
<td style="padding: 20px;">
|
|
<h3 style="margin: 0 0 12px 0; color: {TEXT_COLOR}; font-size: 14px; font-weight: 600;">Invitation Details:</h3>
|
|
<p style="margin: 0; color: {TEXT_COLOR}; font-size: 14px;"><strong>Organization:</strong> {org_name}</p>
|
|
<p style="margin: 8px 0 0 0; color: {TEXT_COLOR}; font-size: 14px;"><strong>Role:</strong> {role}</p>
|
|
<p style="margin: 8px 0 0 0; color: {MUTED_COLOR}; font-size: 13px;">This invitation expires in {expiry_days} days</p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
{get_action_button(invite_link, "Accept Invitation", SUCCESS_COLOR)}
|
|
<p style="margin: 20px 0; color: {MUTED_COLOR}; font-size: 13px;">
|
|
If you did not expect this invitation, you can safely ignore this email.
|
|
</p>
|
|
'''
|
|
return get_base_html(content, f"You're invited to join {org_name} on Secuird", f"You've been invited to join {org_name}")
|
|
|
|
|
|
def build_email_verification_resend_html(
|
|
user_name: str,
|
|
verify_link: str,
|
|
expiry_hours: int = 24,
|
|
) -> str:
|
|
"""Build email verification resend email.
|
|
|
|
Args:
|
|
user_name: Recipient's name or email
|
|
verify_link: Verification link URL
|
|
expiry_hours: Hours until link expires
|
|
|
|
Returns:
|
|
HTML email string
|
|
"""
|
|
content = f'''
|
|
<h2 style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 20px; font-weight: 600;">Verify Your Email Address</h2>
|
|
<p style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 15px; line-height: 1.6;">
|
|
Hi <strong>{user_name}</strong>,
|
|
</p>
|
|
<p style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 15px; line-height: 1.6;">
|
|
Please verify your email address by clicking the button below:
|
|
</p>
|
|
{get_action_button(verify_link, "Verify Email Address")}
|
|
<p style="margin: 20px 0; color: {MUTED_COLOR}; font-size: 13px;">
|
|
This link will expire in <strong>{expiry_hours} hours</strong>.
|
|
</p>
|
|
{get_alert_box("If you didn't request this, you can safely ignore this email.", "info", "🔒")}
|
|
'''
|
|
return get_base_html(content, "Verify your Secuird email address", "Please verify your email address")
|
|
|
|
|
|
def build_contact_enquiry_html(
|
|
enquiry_type: str,
|
|
submitter_email: str,
|
|
name: Optional[str],
|
|
company: Optional[str],
|
|
interest_area: Optional[str],
|
|
message: Optional[str],
|
|
) -> str:
|
|
"""Build a contact enquiry notification email.
|
|
|
|
Args:
|
|
enquiry_type: One of demo_request, sales_enquiry, general, support
|
|
submitter_email: Email address of the person submitting the enquiry
|
|
name: Full name of the submitter (optional)
|
|
company: Company name (optional)
|
|
interest_area: Area of interest (optional)
|
|
message: Free-text message (optional)
|
|
|
|
Returns:
|
|
HTML email string
|
|
"""
|
|
# Map enquiry types to display labels and colors
|
|
type_labels = {
|
|
"demo_request": ("Demo Request", "info"),
|
|
"sales_enquiry": ("Sales Enquiry", "success"),
|
|
"general": ("General Enquiry", "info"),
|
|
"support": ("Support Request", "warning"),
|
|
}
|
|
type_label, alert_type = type_labels.get(enquiry_type, ("Enquiry", "info"))
|
|
|
|
name_display = name if name else "Not provided"
|
|
company_display = company if company else "Not provided"
|
|
interest_display = interest_area if interest_area else "Not provided"
|
|
message_display = message if message else "No message provided"
|
|
|
|
# Build details table
|
|
details_rows = f"""
|
|
{get_detail_row("Enquiry Type", type_label)}
|
|
{get_detail_row("Submitter Email", submitter_email)}
|
|
{get_detail_row("Name", name_display)}
|
|
{get_detail_row("Company", company_display)}
|
|
{get_detail_row("Interest Area", interest_display)}
|
|
"""
|
|
|
|
content = f'''
|
|
<h2 style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 20px; font-weight: 600;">New {type_label}</h2>
|
|
<p style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 15px; line-height: 1.6;">
|
|
A new {type_label.lower()} has been submitted through the Secuird website.
|
|
</p>
|
|
{get_alert_box(f"Enquiry type: <strong>{type_label}</strong>", alert_type, "📬")}
|
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="margin: 20px 0; background-color: {BACKGROUND_COLOR}; border-radius: 8px;">
|
|
<tr>
|
|
<td style="padding: 20px;">
|
|
<h3 style="margin: 0 0 16px 0; color: {TEXT_COLOR}; font-size: 14px; font-weight: 600;">Enquiry Details</h3>
|
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0">
|
|
{details_rows}
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<h3 style="margin: 20px 0 12px 0; color: {TEXT_COLOR}; font-size: 14px; font-weight: 600;">Message</h3>
|
|
<p style="margin: 0; color: {TEXT_COLOR}; font-size: 14px; line-height: 1.6; white-space: pre-wrap;">{message_display}</p>
|
|
'''
|
|
return get_base_html(content, f"Secuird Website: {type_label}", f"New {type_label} from {submitter_email}")
|
|
|
|
|
|
def build_invite_accepted_html(
|
|
inviter_name: str,
|
|
member_name: str,
|
|
member_email: str,
|
|
org_name: str,
|
|
role: str,
|
|
org_link: Optional[str] = None,
|
|
) -> str:
|
|
"""Build invite accepted notification email.
|
|
|
|
Args:
|
|
inviter_name: Name of the person who sent the invite
|
|
member_name: Name of the person who accepted
|
|
member_email: Email of the person who accepted
|
|
org_name: Organization name
|
|
role: Role assigned to the member
|
|
org_link: Optional link to view the organization
|
|
|
|
Returns:
|
|
HTML email string
|
|
"""
|
|
content = f'''
|
|
<h2 style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 20px; font-weight: 600;">Invitation Accepted</h2>
|
|
<p style="margin: 0 0 20px 0; color: {TEXT_COLOR}; font-size: 15px; line-height: 1.6;">
|
|
<strong>{member_name}</strong> has accepted your invitation to join <strong>{org_name}</strong> on Secuird.
|
|
</p>
|
|
{get_alert_box(f"<strong>{member_name}</strong> ({member_email}) has joined <strong>{org_name}</strong>", "success", "✅")}
|
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="margin: 20px 0; background-color: {BACKGROUND_COLOR}; border-radius: 8px;">
|
|
<tr>
|
|
<td style="padding: 20px;">
|
|
<h3 style="margin: 0 0 16px 0; color: {TEXT_COLOR}; font-size: 14px; font-weight: 600;">Membership Details</h3>
|
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0">
|
|
{get_detail_row("Member", member_name)}
|
|
{get_detail_row("Email", member_email)}
|
|
{get_detail_row("Organization", org_name)}
|
|
{get_detail_row("Role", role)}
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
'''
|
|
if org_link:
|
|
content += get_action_button(org_link, "View Organization", PRIMARY_COLOR)
|
|
|
|
return get_base_html(content, f"Invitation accepted: {org_name}", f"{member_name} has joined {org_name}")
|
|
|