Add HTML email templates for magic link sign-in
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 49s

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 <noreply@anthropic.com>
This commit is contained in:
giles
2026-02-18 15:09:49 +00:00
parent 23b1e35eac
commit dd7fbc89ce
3 changed files with 49 additions and 11 deletions

View File

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

View File

@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8"></head>
<body style="margin:0;padding:0;background:#f5f5f4;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
<table width="100%" cellpadding="0" cellspacing="0" style="background:#f5f5f4;padding:40px 0;">
<tr><td align="center">
<table width="480" cellpadding="0" cellspacing="0" style="background:#ffffff;border-radius:12px;border:1px solid #e7e5e4;padding:40px;">
<tr><td>
<h1 style="margin:0 0 8px;font-size:20px;font-weight:600;color:#1c1917;">{{ site_name }}</h1>
<p style="margin:0 0 24px;font-size:15px;color:#57534e;">Sign in to your account</p>
<p style="margin:0 0 24px;font-size:15px;line-height:1.5;color:#44403c;">
Click the button below to sign in. This link will expire in 15&nbsp;minutes.
</p>
<table cellpadding="0" cellspacing="0" style="margin:0 0 24px;"><tr><td style="border-radius:8px;background:#1c1917;">
<a href="{{ link_url }}" target="_blank"
style="display:inline-block;padding:12px 32px;font-size:15px;font-weight:500;color:#ffffff;text-decoration:none;border-radius:8px;">
Sign in
</a>
</td></tr></table>
<p style="margin:0 0 8px;font-size:13px;color:#78716c;">Or copy and paste this link into your browser:</p>
<p style="margin:0 0 24px;font-size:13px;word-break:break-all;">
<a href="{{ link_url }}" style="color:#1c1917;">{{ link_url }}</a>
</p>
<hr style="border:none;border-top:1px solid #e7e5e4;margin:24px 0;">
<p style="margin:0;font-size:12px;color:#a8a29e;">
If you did not request this email, you can safely ignore it.
</p>
</td></tr>
</table>
</td></tr>
</table>
</body>
</html>

View File

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