From dd7fbc89ceed50da6a9d967e695ccb2e6c922ec8 Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 18 Feb 2026 15:09:49 +0000 Subject: [PATCH] Add HTML email templates for magic link sign-in Extract inline email body into separate Jinja2 templates (_email/magic_link.html and .txt) with a styled sign-in button and fallback plain-text link. Co-Authored-By: Claude Opus 4.6 --- bp/auth/services/auth_operations.py | 19 +++++++---------- templates/_email/magic_link.html | 33 +++++++++++++++++++++++++++++ templates/_email/magic_link.txt | 8 +++++++ 3 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 templates/_email/magic_link.html create mode 100644 templates/_email/magic_link.txt diff --git a/bp/auth/services/auth_operations.py b/bp/auth/services/auth_operations.py index a0d0d9c..d9964a4 100644 --- a/bp/auth/services/auth_operations.py +++ b/bp/auth/services/auth_operations.py @@ -5,7 +5,7 @@ import secrets from datetime import datetime, timedelta, timezone from typing import Optional, Tuple -from quart import current_app, request, g +from quart import current_app, render_template, request, g from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload @@ -45,16 +45,12 @@ async def send_magic_email(to_email: str, link_url: str) -> None: password = os.getenv("SMTP_PASS") mail_from = os.getenv("MAIL_FROM") or "no-reply@example.com" - subject = "Your sign-in link" - body = f"""Hello, + site_name = config().get("title", "Rose Ash") + subject = f"Your sign-in link — {site_name}" -Click this link to sign in: -{link_url} - -This link will expire in 15 minutes. - -If you did not request this, you can ignore this email. -""" + tpl_vars = dict(site_name=site_name, link_url=link_url) + text_body = await render_template("_email/magic_link.txt", **tpl_vars) + html_body = await render_template("_email/magic_link.html", **tpl_vars) if not host or not username or not password: # Fallback: log to console @@ -74,7 +70,8 @@ If you did not request this, you can ignore this email. msg["From"] = mail_from msg["To"] = to_email msg["Subject"] = subject - msg.set_content(body) + msg.set_content(text_body) + msg.add_alternative(html_body, subtype="html") is_secure = port == 465 # implicit TLS if true if is_secure: diff --git a/templates/_email/magic_link.html b/templates/_email/magic_link.html new file mode 100644 index 0000000..3c1eac6 --- /dev/null +++ b/templates/_email/magic_link.html @@ -0,0 +1,33 @@ + + + + + + +
+ + +
+

{{ site_name }}

+

Sign in to your account

+

+ Click the button below to sign in. This link will expire in 15 minutes. +

+
+ + Sign in + +
+

Or copy and paste this link into your browser:

+

+ {{ link_url }} +

+
+

+ If you did not request this email, you can safely ignore it. +

+
+
+ + diff --git a/templates/_email/magic_link.txt b/templates/_email/magic_link.txt new file mode 100644 index 0000000..28a2efb --- /dev/null +++ b/templates/_email/magic_link.txt @@ -0,0 +1,8 @@ +Hello, + +Click this link to sign in: +{{ link_url }} + +This link will expire in 15 minutes. + +If you did not request this, you can ignore this email.