Redesign claim-approved and password-reset emails as branded HTML
Confirmed Resend's /emails endpoint accepts an html field alongside text. Claim-approved email now carries an HTML alternative with the club's logo/colours; allauth's password-reset email is overridden with the same treatment, falling back to RosterChief's own branding outside a club context. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
78
templates/account/email/password_reset_key_message.html
Normal file
78
templates/account/email/password_reset_key_message.html
Normal file
@@ -0,0 +1,78 @@
|
||||
{% extends "email/_base.html" %}
|
||||
{% load i18n club_email %}
|
||||
|
||||
{% comment %}
|
||||
allauth's DefaultAccountAdapter.render_mail looks for
|
||||
"<template_prefix>_message.<TEMPLATE_EXTENSION>" (TEMPLATE_EXTENSION
|
||||
defaults to "html", unset in this project) next to the .txt template it
|
||||
already renders, and attaches it as the HTML alternative automatically --
|
||||
no Python override needed, see allauth.account.adapter.render_mail.
|
||||
|
||||
Context comes from allauth.account.internal.flows.password_reset
|
||||
.request_password_reset() plus DefaultAccountAdapter.send_mail: user,
|
||||
password_reset_url, uid, key, request, current_site, email, and username
|
||||
only when ACCOUNT_LOGIN_METHODS includes "username" (it doesn't here, see
|
||||
rosterchief/settings.py -- login is email-only -- but the check is kept for
|
||||
parity with the .txt template in case that ever changes).
|
||||
|
||||
render_to_string is called with the real `request`
|
||||
(allauth.account.adapter.render_mail passes `context.request`), so this
|
||||
picks up club.context_processors.branding like any other page: `club` is
|
||||
set when the reset was requested on a club subdomain, None on the base
|
||||
domain (e.g. a platform control-panel user). Both cases must render
|
||||
something sensible -- a password reset is exactly the kind of email that
|
||||
still has to work with no club in play.
|
||||
{% endcomment %}
|
||||
|
||||
{% block title %}{% trans "Reset your password" %}{% endblock title %}
|
||||
|
||||
{% block preheader %}{% trans "Use this link to choose a new password. If you didn't request this, you can ignore this email." %}{% endblock preheader %}
|
||||
|
||||
{% block header %}
|
||||
{% if club %}
|
||||
{% absolute_media_url club.logo as logo_url %}
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td valign="middle">
|
||||
{% if club.logo %}
|
||||
<img src="{{ logo_url }}" alt="{{ club.name }}" width="48" height="48" style="display:block; width:48px; height:48px; border-radius:24px; object-fit:contain; background-color:#f3f4f6;">
|
||||
{% else %}
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="48" style="width:48px;">
|
||||
<tr>
|
||||
<td align="center" valign="middle" width="48" height="48" bgcolor="{{ club.secondary_color|default:"#ec4899" }}" style="width:48px; height:48px; border-radius:24px; background-color:{{ club.secondary_color|default:"#ec4899" }}; color:{{ club.secondary_content_color|default:"#ffffff" }}; font-family: Arial, Helvetica, sans-serif; font-size:16px; font-weight:bold;">
|
||||
{{ club.initials }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="padding-left:14px;" valign="middle">
|
||||
<span style="font-family: Arial, Helvetica, sans-serif; font-size:18px; font-weight:bold; color:#111827;">{{ club.name }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% else %}
|
||||
<span style="font-family: Arial, Helvetica, sans-serif; font-size:20px; font-weight:bold; color:#111827; letter-spacing:0.02em;">Roster<span style="color:#0ea5e9;">Chief</span></span>
|
||||
{% endif %}
|
||||
{% endblock header %}
|
||||
|
||||
{% block content %}
|
||||
<p style="margin:0 0 20px 0;">{% trans "You're receiving this email because you or someone else has requested a password reset for your user account. It can be safely ignored if you did not request one." %}</p>
|
||||
|
||||
{% trans "Reset your password" as reset_label %}
|
||||
{% include "email/_button.html" with href=password_reset_url label=reset_label bg=club.primary_color|default:"#0ea5e9" fg=club.primary_content_color|default:"#ffffff" %}
|
||||
|
||||
{% if username %}
|
||||
<p style="margin:0;">{% blocktrans %}In case you forgot, your username is {{ username }}.{% endblocktrans %}</p>
|
||||
{% endif %}
|
||||
{% endblock content %}
|
||||
|
||||
{% block footer %}
|
||||
<p style="margin:0;">
|
||||
{% if club %}
|
||||
{% blocktrans with club=club.name %}Sent by {{ club }}, powered by RosterChief.{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans with site_name=current_site.name %}Sent by {{ site_name }}.{% endblocktrans %}
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endblock footer %}
|
||||
52
templates/email/_base.html
Normal file
52
templates/email/_base.html
Normal file
@@ -0,0 +1,52 @@
|
||||
{% load i18n %}<!DOCTYPE html>
|
||||
{% comment %}
|
||||
Shared shell for every transactional HTML email (the claim-approved parent
|
||||
email, allauth's password-reset override, and anything after it). Deliberately
|
||||
not a responsive email framework: a single ~600px table with generous padding
|
||||
reads fine on a phone without media queries, and media queries are one of the
|
||||
first things Outlook desktop and a lot of webmail strip anyway.
|
||||
|
||||
Everything is inline styles on table-based markup on purpose -- no <style>
|
||||
block, no linked stylesheet (daisyUI/Tailwind's compiled CSS is a web asset,
|
||||
not an email-safe one, and most inboxes strip a <style> block regardless).
|
||||
|
||||
Subclasses override `header`, `content` and `footer`; `preheader` is the
|
||||
hidden preview-pane text most inboxes show next to the subject line.
|
||||
{% endcomment %}
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>{% block title %}{% endblock title %}</title>
|
||||
</head>
|
||||
<body style="margin:0; padding:0; background-color:#f3f4f6; font-family: Arial, Helvetica, sans-serif;">
|
||||
<div style="display:none; max-height:0; max-width:0; overflow:hidden; opacity:0; mso-hide:all;">
|
||||
{% block preheader %}{% endblock preheader %}
|
||||
</div>
|
||||
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:#f3f4f6;">
|
||||
<tr>
|
||||
<td align="center" style="padding: 24px 16px;">
|
||||
<table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0" style="width:600px; max-width:600px; background-color:#ffffff; border-radius:8px;">
|
||||
<tr>
|
||||
<td style="padding: 28px 40px; border-bottom: 1px solid #e5e7eb;">
|
||||
{% block header %}{% endblock header %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 32px 40px; color:#1f2933; font-size:15px; line-height:1.6;">
|
||||
{% block content %}{% endblock content %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 20px 40px 28px 40px; border-top: 1px solid #e5e7eb; color:#6b7280; font-size:12px; line-height:1.5;">
|
||||
{% block footer %}{% endblock footer %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
18
templates/email/_button.html
Normal file
18
templates/email/_button.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{% comment %}
|
||||
A single call-to-action button, table-wrapped so it centres reliably in
|
||||
Outlook desktop (which ignores margin: 0 auto on a bare <a>). Include with:
|
||||
|
||||
{% include "email/_button.html" with href=set_password_url label=_("Set your password") bg="#0ea5e9" fg="#ffffff" %}
|
||||
|
||||
`bg`/`fg` are plain hex strings (already resolved -- e.g. club.primary_color
|
||||
with a fallback), not template logic, so this stays a dumb, reusable snippet.
|
||||
{% endcomment %}
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin: 8px 0 24px 0;">
|
||||
<tr>
|
||||
<td align="center" bgcolor="{{ bg }}" style="border-radius:6px; background-color:{{ bg }};">
|
||||
<a href="{{ href }}" style="display:inline-block; padding:12px 28px; font-family: Arial, Helvetica, sans-serif; font-size:15px; font-weight:bold; color:{{ fg }}; text-decoration:none; border-radius:6px;">
|
||||
{{ label }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
Reference in New Issue
Block a user