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:
@@ -12,6 +12,46 @@ from .core import config, execute, fetch_all, init_db, send_email
|
||||
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;">© {config.APP_NAME} · 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):
|
||||
"""Decorator to register a task handler."""
|
||||
def decorator(f):
|
||||
@@ -105,35 +145,37 @@ async def handle_send_email(payload: dict) -> None:
|
||||
async def handle_send_magic_link(payload: dict) -> None:
|
||||
"""Send magic link email."""
|
||||
link = f"{config.BASE_URL}/auth/verify?token={payload['token']}"
|
||||
|
||||
html = f"""
|
||||
<h2>Sign in to {config.APP_NAME}</h2>
|
||||
<p>Click the link below to sign in:</p>
|
||||
<p><a href="{link}">{link}</a></p>
|
||||
<p>This link expires in {config.MAGIC_LINK_EXPIRY_MINUTES} minutes.</p>
|
||||
<p>If you didn't request this, you can safely ignore this email.</p>
|
||||
"""
|
||||
|
||||
|
||||
body = (
|
||||
f'<h2 style="margin:0 0 16px;color:#0F172A;font-size:20px;">Sign in to {config.APP_NAME}</h2>'
|
||||
f"<p>Click the button below to sign in. This link expires in "
|
||||
f"{config.MAGIC_LINK_EXPIRY_MINUTES} minutes.</p>"
|
||||
f"{_email_button(link, 'Sign In')}"
|
||||
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(
|
||||
to=payload["email"],
|
||||
subject=f"Sign in to {config.APP_NAME}",
|
||||
html=html,
|
||||
html=_email_wrap(body),
|
||||
)
|
||||
|
||||
|
||||
@task("send_welcome")
|
||||
async def handle_send_welcome(payload: dict) -> None:
|
||||
"""Send welcome email to new user."""
|
||||
html = f"""
|
||||
<h2>Welcome to {config.APP_NAME}!</h2>
|
||||
<p>Thanks for signing up. We're excited to have you.</p>
|
||||
<p><a href="{config.BASE_URL}/dashboard">Go to your dashboard</a></p>
|
||||
"""
|
||||
|
||||
body = (
|
||||
f'<h2 style="margin:0 0 16px;color:#0F172A;font-size:20px;">Welcome to {config.APP_NAME}!</h2>'
|
||||
f"<p>Thanks for signing up. You're all set to start planning your padel business.</p>"
|
||||
f'{_email_button(f"{config.BASE_URL}/dashboard", "Go to Dashboard")}'
|
||||
)
|
||||
|
||||
await send_email(
|
||||
to=payload["email"],
|
||||
subject=f"Welcome to {config.APP_NAME}",
|
||||
html=html,
|
||||
html=_email_wrap(body),
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user