style transactional emails with branded layout

Wrap magic link and welcome emails in a proper HTML email template
with navy header, white card body, CTA button, and muted footer.
Fallback plain-text URL included below the button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Deeman
2026-02-16 16:29:13 +01:00
parent 72077fdd46
commit 1d744bbf6d

View File

@@ -12,6 +12,46 @@ from .core import config, execute, fetch_all, init_db, send_email
HANDLERS: dict[str, callable] = {} HANDLERS: dict[str, callable] = {}
def _email_wrap(body: str) -> str:
"""Wrap email body in a branded layout with inline CSS."""
return f"""\
<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8"></head>
<body style="margin:0;padding:0;background-color:#F8FAFC;font-family:'Inter',Helvetica,Arial,sans-serif;">
<table width="100%" cellpadding="0" cellspacing="0" style="background-color:#F8FAFC;padding:40px 0;">
<tr><td align="center">
<table width="480" cellpadding="0" cellspacing="0" style="background-color:#FFFFFF;border-radius:8px;border:1px solid #E2E8F0;overflow:hidden;">
<!-- Header -->
<tr><td style="background-color:#0F172A;padding:24px 32px;">
<span style="color:#FFFFFF;font-size:18px;font-weight:700;letter-spacing:-0.02em;">{config.APP_NAME}</span>
</td></tr>
<!-- Body -->
<tr><td style="padding:32px;color:#475569;font-size:15px;line-height:1.6;">
{body}
</td></tr>
<!-- Footer -->
<tr><td style="padding:20px 32px;border-top:1px solid #E2E8F0;text-align:center;">
<span style="color:#94A3B8;font-size:12px;">&copy; {config.APP_NAME} &middot; You received this because you have an account.</span>
</td></tr>
</table>
</td></tr>
</table>
</body>
</html>"""
def _email_button(url: str, label: str) -> str:
"""Render a branded CTA button for email."""
return (
f'<table cellpadding="0" cellspacing="0" style="margin:24px 0;">'
f'<tr><td style="background-color:#3B82F6;border-radius:6px;text-align:center;">'
f'<a href="{url}" style="display:inline-block;padding:12px 28px;'
f'color:#FFFFFF;font-size:15px;font-weight:600;text-decoration:none;">'
f'{label}</a></td></tr></table>'
)
def task(name: str): def task(name: str):
"""Decorator to register a task handler.""" """Decorator to register a task handler."""
def decorator(f): def decorator(f):
@@ -105,35 +145,37 @@ async def handle_send_email(payload: dict) -> None:
async def handle_send_magic_link(payload: dict) -> None: async def handle_send_magic_link(payload: dict) -> None:
"""Send magic link email.""" """Send magic link email."""
link = f"{config.BASE_URL}/auth/verify?token={payload['token']}" link = f"{config.BASE_URL}/auth/verify?token={payload['token']}"
html = f""" body = (
<h2>Sign in to {config.APP_NAME}</h2> f'<h2 style="margin:0 0 16px;color:#0F172A;font-size:20px;">Sign in to {config.APP_NAME}</h2>'
<p>Click the link below to sign in:</p> f"<p>Click the button below to sign in. This link expires in "
<p><a href="{link}">{link}</a></p> f"{config.MAGIC_LINK_EXPIRY_MINUTES} minutes.</p>"
<p>This link expires in {config.MAGIC_LINK_EXPIRY_MINUTES} minutes.</p> f"{_email_button(link, 'Sign In')}"
<p>If you didn't request this, you can safely ignore this email.</p> f'<p style="font-size:13px;color:#94A3B8;">If the button doesn\'t work, copy and paste this URL into your browser:</p>'
""" f'<p style="font-size:13px;color:#94A3B8;word-break:break-all;">{link}</p>'
f'<p style="font-size:13px;color:#94A3B8;">If you didn\'t request this, you can safely ignore this email.</p>'
)
await send_email( await send_email(
to=payload["email"], to=payload["email"],
subject=f"Sign in to {config.APP_NAME}", subject=f"Sign in to {config.APP_NAME}",
html=html, html=_email_wrap(body),
) )
@task("send_welcome") @task("send_welcome")
async def handle_send_welcome(payload: dict) -> None: async def handle_send_welcome(payload: dict) -> None:
"""Send welcome email to new user.""" """Send welcome email to new user."""
html = f""" body = (
<h2>Welcome to {config.APP_NAME}!</h2> f'<h2 style="margin:0 0 16px;color:#0F172A;font-size:20px;">Welcome to {config.APP_NAME}!</h2>'
<p>Thanks for signing up. We're excited to have you.</p> f"<p>Thanks for signing up. You're all set to start planning your padel business.</p>"
<p><a href="{config.BASE_URL}/dashboard">Go to your dashboard</a></p> f'{_email_button(f"{config.BASE_URL}/dashboard", "Go to Dashboard")}'
""" )
await send_email( await send_email(
to=payload["email"], to=payload["email"],
subject=f"Welcome to {config.APP_NAME}", subject=f"Welcome to {config.APP_NAME}",
html=html, html=_email_wrap(body),
) )