feat(email): use HTML templates for all transactional emails

- Update org invite, password reset, email verification, and account activation emails to use HTML templates
- Update MFA deadline reminder and suspension notifications to use HTML templates
- Add html_body parameter to _send_email_async for rich email content
This commit is contained in:
2026-04-05 15:44:22 +00:00
parent f2386ed1da
commit 6325d60097
5 changed files with 127 additions and 50 deletions
+66 -8
View File
@@ -25,6 +25,10 @@ from gatehouse_app.models.security.organization_security_policy import Organizat
from gatehouse_app.models.user.user import User
from gatehouse_app.services.audit_service import AuditService
from gatehouse_app.services.email_provider import EmailMessage, EmailProviderFactory
from gatehouse_app.services.email_templates import (
build_mfa_deadline_reminder_html,
build_mfa_suspension_html,
)
from gatehouse_app.utils.constants import AuditAction
logger = logging.getLogger(__name__)
@@ -73,17 +77,46 @@ class NotificationService:
now = datetime.now(timezone.utc)
days_until_deadline = (deadline - now).days
# Build notification content
# Get organization name
org_name = compliance.organization_id
from gatehouse_app.models.organization.organization import Organization
org = Organization.query.get(compliance.organization_id)
if org:
org_name = org.name
# Build required MFA methods string
from gatehouse_app.utils.constants import MfaPolicyMode
mfa_methods = "Multi-factor authentication"
mode = org_policy.mfa_policy_mode
if mode == MfaPolicyMode.REQUIRE_TOTP:
mfa_methods = "Authenticator app (TOTP)"
elif mode == MfaPolicyMode.REQUIRE_WEBAUTHN:
mfa_methods = "Passkey (WebAuthn)"
elif mode == MfaPolicyMode.REQUIRE_TOTP_OR_WEBAUTHN:
mfa_methods = "Authenticator app (TOTP) OR Passkey (WebAuthn)"
deadline_date = compliance.deadline_at.strftime('%Y-%m-%d %H:%M UTC') if compliance.deadline_at else 'Not set'
# Build HTML email
app_url = current_app.config.get("APP_URL", "http://localhost:8080")
setup_link = f"{app_url}/settings/security"
subject = f"Action Required: MFA enrollment deadline in {days_until_deadline} days"
body = NotificationService._build_deadline_reminder_body(
user, compliance, org_policy, days_until_deadline
html_body = build_mfa_deadline_reminder_html(
user_name=user.full_name or user.email,
org_name=org_name,
days_remaining=days_until_deadline,
deadline_date=deadline_date,
mfa_methods=mfa_methods,
setup_link=setup_link,
)
# Send the notification
NotificationService._send_email_async(
to_address=user.email,
subject=subject,
body=body,
body=f"MFA enrollment deadline in {days_until_deadline} days: {setup_link}",
html_body=html_body,
)
logger.info(
f"Sent MFA deadline reminder to {user.email} "
@@ -124,17 +157,42 @@ class NotificationService:
True if notification was sent successfully, False otherwise
"""
try:
# Build notification content
# Get organization name
org_name = compliance.organization_id
from gatehouse_app.models.organization.organization import Organization
org = Organization.query.get(compliance.organization_id)
if org:
org_name = org.name
# Build required MFA methods string
from gatehouse_app.utils.constants import MfaPolicyMode
mfa_methods = "Multi-factor authentication"
mode = org_policy.mfa_policy_mode
if mode == MfaPolicyMode.REQUIRE_TOTP:
mfa_methods = "Authenticator app (TOTP)"
elif mode == MfaPolicyMode.REQUIRE_WEBAUTHN:
mfa_methods = "Passkey (WebAuthn)"
elif mode == MfaPolicyMode.REQUIRE_TOTP_OR_WEBAUTHN:
mfa_methods = "Authenticator app (TOTP) OR Passkey (WebAuthn)"
# Build HTML email
app_url = current_app.config.get("APP_URL", "http://localhost:8080")
setup_link = f"{app_url}/settings/security"
subject = "Account Access Restricted - MFA Enrollment Required"
body = NotificationService._build_suspension_body(
user, compliance, org_policy
html_body = build_mfa_suspension_html(
user_name=user.full_name or user.email,
org_name=org_name,
mfa_methods=mfa_methods,
setup_link=setup_link,
)
# Send the notification
NotificationService._send_email_async(
to_address=user.email,
subject=subject,
body=body,
body=f"Your account has been suspended. Set up MFA to restore access: {setup_link}",
html_body=html_body,
)
logger.info(f"Sent MFA suspension notification to {user.email}")
AuditService.log_action(