feat(email): add provider abstraction and HTML templates

Add pluggable email provider system supporting SMTP, Mailgun, and SendGrid
with factory pattern for runtime provider selection. Includes branded HTML
email templates for verification, password reset, MFA notifications, and
organization invites.

Also rebrands all email content from Gatehouse to Secuird, adds email
provider configuration options, and fixes duplicate log handlers in
development mode.
This commit is contained in:
2026-04-04 16:55:00 +10:30
parent d90a06437e
commit 41bbdb4bef
17 changed files with 1068 additions and 76 deletions
+3 -3
View File
@@ -32,12 +32,12 @@ def register():
verify_token = EmailVerificationToken.generate(user_id=user.id)
app_url = current_app.config.get("APP_URL", "http://localhost:8080")
verify_link = f"{app_url}/verify-email?token={verify_token.token}"
subject = "Verify your Gatehouse email address"
subject = "Verify your Secuird email address"
body = (
f"Hi {user.full_name or user.email},\n\n"
f"Welcome to Gatehouse! Please verify your email address by clicking the link below (valid for 24 hours):\n"
f"Welcome to Secuird! Please verify your email address by clicking the link below (valid for 24 hours):\n"
f"{verify_link}\n\n"
f"Gatehouse Security Team"
f"Secuird Security Team"
)
NotificationService._send_email_async(to_address=user.email, subject=subject, body=body)
except Exception as exc:
+8 -8
View File
@@ -29,14 +29,14 @@ def forgot_password():
reset_link = f"{app_url}/reset-password?token={reset_token.token}"
NotificationService._send_email_async(
to_address=user.email,
subject="Reset your Gatehouse password",
subject="Reset your Secuird password",
body=(
f"Hi {user.full_name or user.email},\n\n"
f"You requested a password reset for your Gatehouse account.\n\n"
f"You requested a password reset for your Secuird account.\n\n"
f"Click the link below to reset your password (valid for 2 hours):\n"
f"{reset_link}\n\n"
f"If you did not request this, you can safely ignore this email.\n\n"
f"Gatehouse Security Team"
f"Secuird Security Team"
),
)
_logger.info(f"Password reset token generated for user {user.id}")
@@ -131,12 +131,12 @@ def resend_verification():
verify_link = f"{app_url}/verify-email?token={verify_token.token}"
NotificationService._send_email_async(
to_address=user.email,
subject="Verify your Gatehouse email address",
subject="Verify your Secuird email address",
body=(
f"Hi {user.full_name or user.email},\n\n"
f"Please verify your email address by clicking the link below (valid for 24 hours):\n"
f"{verify_link}\n\n"
f"Gatehouse Security Team"
f"Secuird Security Team"
),
)
_logger.info(f"Verification email sent for user {user.id}")
@@ -202,13 +202,13 @@ def resend_activation():
activate_link = f"{app_url}/activate?code={code}"
NotificationService._send_email_async(
to_address=user.email,
subject="Activate your Gatehouse account",
subject="Activate your Secuird account",
body=(
f"Hi {user.full_name or user.email},\n\n"
f"Please activate your Gatehouse account by clicking the link below:\n"
f"Please activate your Secuird account by clicking the link below:\n"
f"{activate_link}\n\n"
f"If you did not create an account, you can safely ignore this email.\n\n"
f"Gatehouse Security Team"
f"Secuird Security Team"
),
)
_logger.info(f"Activation email re-sent to {user.id}")