Update all email templates with uniform branded graphics

This commit is contained in:
2026-03-03 17:02:38 +13:00
parent 1ff54690fc
commit d80b1e5e4f
5 changed files with 293 additions and 101 deletions

View File

@@ -1 +1 @@
0303261629
0303261702

File diff suppressed because one or more lines are too long

View File

@@ -136,6 +136,108 @@ TEMPLATE_PRESENTATION: Dict[str, Dict[str, str]] = {
},
}
def _build_email_stat_card(label: str, value: str, detail: str = "") -> str:
detail_html = (
f"<div style=\"margin-top:8px; font-size:13px; line-height:1.6; color:#9aa3b8; word-break:break-word;\">"
f"{html.escape(detail)}</div>"
if detail
else ""
)
return (
"<div style=\"padding:16px; background:#151c2d; border:1px solid rgba(255,255,255,0.08); "
"border-radius:16px; color:#e9ecf5;\">"
f"<div style=\"font-size:11px; letter-spacing:0.12em; text-transform:uppercase; color:#9aa3b8; "
f"margin-bottom:8px;\">{html.escape(label)}</div>"
f"<div style=\"font-size:20px; font-weight:800; line-height:1.45; word-break:break-word;\">"
f"{html.escape(value)}</div>"
f"{detail_html}"
"</div>"
)
def _build_email_stat_grid(cards: list[str]) -> str:
if not cards:
return ""
rows: list[str] = []
for index in range(0, len(cards), 2):
left = cards[index]
right = cards[index + 1] if index + 1 < len(cards) else ""
rows.append(
"<tr>"
f"<td width=\"50%\" style=\"vertical-align:top; padding:0 5px 10px 0;\">{left}</td>"
f"<td width=\"50%\" style=\"vertical-align:top; padding:0 0 10px 5px;\">{right}</td>"
"</tr>"
)
return (
"<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" "
"style=\"border-collapse:collapse; margin:0 0 18px;\">"
f"{''.join(rows)}"
"</table>"
)
def _build_email_list(items: list[str], *, ordered: bool = False) -> str:
tag = "ol" if ordered else "ul"
marker = "padding-left:20px;" if ordered else "padding-left:18px;"
rendered_items = "".join(
f"<li style=\"margin:0 0 8px;\">{html.escape(item)}</li>" for item in items if item
)
return (
f"<{tag} style=\"margin:0; {marker} color:#dbe5ff; line-height:1.8; font-size:14px;\">"
f"{rendered_items}"
f"</{tag}>"
)
def _build_email_panel(title: str, body_html: str, *, variant: str = "neutral") -> str:
styles = {
"neutral": {
"background": "#101726",
"border": "rgba(255,255,255,0.08)",
"eyebrow": "#9aa3b8",
"text": "#dbe5ff",
},
"brand": {
"background": "#101726",
"border": "rgba(59,130,246,0.22)",
"eyebrow": "#9dbfff",
"text": "#dbe5ff",
},
"success": {
"background": "#122016",
"border": "rgba(34,197,94,0.24)",
"eyebrow": "#9de7b5",
"text": "#d9f9e4",
},
"warning": {
"background": "#241814",
"border": "rgba(251,146,60,0.34)",
"eyebrow": "#fbbd7b",
"text": "#ffe0ba",
},
"danger": {
"background": "#251418",
"border": "rgba(239,68,68,0.32)",
"eyebrow": "#ff9b9b",
"text": "#ffd0d0",
},
}.get(variant, {
"background": "#101726",
"border": "rgba(255,255,255,0.08)",
"eyebrow": "#9aa3b8",
"text": "#dbe5ff",
})
return (
f"<div style=\"margin:0 0 18px; padding:18px; background:{styles['background']}; "
f"border:1px solid {styles['border']}; border-radius:18px; color:{styles['text']};\">"
f"<div style=\"font-size:11px; letter-spacing:0.12em; text-transform:uppercase; color:{styles['eyebrow']}; "
f"margin-bottom:10px;\">{html.escape(title)}</div>"
f"<div style=\"font-size:14px; line-height:1.8; color:{styles['text']};\">{body_html}</div>"
"</div>"
)
DEFAULT_TEMPLATES: Dict[str, Dict[str, str]] = {
"invited": {
"subject": "{{app_name}} invite for {{recipient_email}}",
@@ -156,31 +258,40 @@ DEFAULT_TEMPLATES: Dict[str, Dict[str, str]] = {
"<div style=\"margin:0 0 20px; color:#e9ecf5; font-size:15px; line-height:1.7;\">"
"A new invitation has been prepared for <strong>{{recipient_email}}</strong>. Use the details below to sign up."
"</div>"
"<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"border-collapse:separate; border-spacing:10px 10px; margin:0 0 18px;\">"
"<tr>"
"<td width=\"50%\" style=\"padding:16px; background:#151c2d; border:1px solid rgba(255,255,255,0.08); border-radius:16px; color:#e9ecf5;\">"
"<div style=\"font-size:11px; letter-spacing:0.12em; text-transform:uppercase; color:#9aa3b8; margin-bottom:8px;\">Invite code</div>"
"<div style=\"font-size:24px; font-weight:800; letter-spacing:0.06em;\">{{invite_code}}</div>"
"</td>"
"<td width=\"50%\" style=\"padding:16px; background:#151c2d; border:1px solid rgba(255,255,255,0.08); border-radius:16px; color:#e9ecf5;\">"
"<div style=\"font-size:11px; letter-spacing:0.12em; text-transform:uppercase; color:#9aa3b8; margin-bottom:8px;\">Invited by</div>"
"<div style=\"font-size:20px; font-weight:700;\">{{inviter_username}}</div>"
"</td>"
"</tr>"
"<tr>"
"<td width=\"50%\" style=\"padding:16px; background:#151c2d; border:1px solid rgba(255,255,255,0.08); border-radius:16px; color:#e9ecf5;\">"
"<div style=\"font-size:11px; letter-spacing:0.12em; text-transform:uppercase; color:#9aa3b8; margin-bottom:8px;\">Invite label</div>"
"<div style=\"font-size:18px; font-weight:700;\">{{invite_label}}</div>"
"</td>"
"<td width=\"50%\" style=\"padding:16px; background:#151c2d; border:1px solid rgba(255,255,255,0.08); border-radius:16px; color:#e9ecf5;\">"
"<div style=\"font-size:11px; letter-spacing:0.12em; text-transform:uppercase; color:#9aa3b8; margin-bottom:8px;\">Access window</div>"
"<div style=\"font-size:16px; font-weight:700;\">{{invite_expires_at}}</div>"
"<div style=\"margin-top:6px; font-size:13px; color:#9aa3b8;\">Remaining uses: {{invite_remaining_uses}}</div>"
"</td>"
"</tr>"
"</table>"
"<div style=\"margin:0 0 18px; padding:18px; background:#101726; border:1px solid rgba(59,130,246,0.22); border-radius:18px; color:#dbe5ff; font-size:14px; line-height:1.7; white-space:pre-line;\">{{invite_description}}</div>"
"<div style=\"margin:0; padding:18px; background:#101726; border:1px dashed rgba(255,107,43,0.38); border-radius:18px; color:#dbe5ff; font-size:14px; line-height:1.7; white-space:pre-line;\">{{message}}</div>"
+ _build_email_stat_grid(
[
_build_email_stat_card("Invite code", "{{invite_code}}"),
_build_email_stat_card("Invited by", "{{inviter_username}}"),
_build_email_stat_card("Invite label", "{{invite_label}}"),
_build_email_stat_card(
"Access window",
"{{invite_expires_at}}",
"Remaining uses: {{invite_remaining_uses}}",
),
]
)
+ _build_email_panel(
"Invitation details",
"<div style=\"white-space:pre-line;\">{{invite_description}}</div>",
variant="brand",
)
+ _build_email_panel(
"Message from admin",
"<div style=\"white-space:pre-line;\">{{message}}</div>",
variant="neutral",
)
+ _build_email_panel(
"What happens next",
_build_email_list(
[
"Open the invite link and complete the signup flow.",
"Sign in using the shared credentials for Magent and Seerr.",
"Use the How it works page if you want a quick overview first.",
],
ordered=True,
),
variant="neutral",
)
),
},
"welcome": {
@@ -197,27 +308,31 @@ DEFAULT_TEMPLATES: Dict[str, Dict[str, str]] = {
"<div style=\"margin:0 0 18px; color:#e9ecf5; font-size:15px; line-height:1.7;\">"
"Your account is live and ready to use. Everything below mirrors the current site behavior."
"</div>"
"<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"border-collapse:separate; border-spacing:10px 10px; margin:0 0 18px;\">"
"<tr>"
"<td width=\"50%\" style=\"padding:16px; background:#151c2d; border:1px solid rgba(255,255,255,0.08); border-radius:16px; color:#e9ecf5;\">"
"<div style=\"font-size:11px; letter-spacing:0.12em; text-transform:uppercase; color:#9aa3b8; margin-bottom:8px;\">Username</div>"
"<div style=\"font-size:22px; font-weight:800;\">{{username}}</div>"
"</td>"
"<td width=\"50%\" style=\"padding:16px; background:#151c2d; border:1px solid rgba(255,255,255,0.08); border-radius:16px; color:#e9ecf5;\">"
"<div style=\"font-size:11px; letter-spacing:0.12em; text-transform:uppercase; color:#9aa3b8; margin-bottom:8px;\">Role</div>"
"<div style=\"font-size:22px; font-weight:800;\">{{role}}</div>"
"</td>"
"</tr>"
"</table>"
"<div style=\"margin:0 0 18px; padding:18px; background:#101726; border:1px solid rgba(34,197,94,0.24); border-radius:18px; color:#dbe5ff;\">"
"<div style=\"font-size:15px; font-weight:700; margin:0 0 10px;\">What to do next</div>"
"<ol style=\"margin:0; padding-left:20px; color:#dbe5ff; line-height:1.8; font-size:14px;\">"
"<li>Open Magent and sign in using your shared credentials.</li>"
"<li>Search or review requests without refreshing every page.</li>"
"<li>Use the invite tools in your profile if your account allows it.</li>"
"</ol>"
"</div>"
"<div style=\"margin:0; padding:18px; background:#101726; border:1px dashed rgba(59,130,246,0.32); border-radius:18px; color:#dbe5ff; font-size:14px; line-height:1.7; white-space:pre-line;\">{{message}}</div>"
+ _build_email_stat_grid(
[
_build_email_stat_card("Username", "{{username}}"),
_build_email_stat_card("Role", "{{role}}"),
_build_email_stat_card("Magent", "{{app_url}}"),
_build_email_stat_card("Guides", "{{how_it_works_url}}"),
]
)
+ _build_email_panel(
"What to do next",
_build_email_list(
[
"Open Magent and sign in using your shared credentials.",
"Search all requests or review your own activity without refreshing the page.",
"Use the invite tools in your profile if your account allows it.",
],
ordered=True,
),
variant="success",
)
+ _build_email_panel(
"Additional notes",
"<div style=\"white-space:pre-line;\">{{message}}</div>",
variant="neutral",
)
),
},
"warning": {
@@ -233,12 +348,35 @@ DEFAULT_TEMPLATES: Dict[str, Dict[str, str]] = {
"<div style=\"margin:0 0 18px; color:#e9ecf5; font-size:15px; line-height:1.7;\">"
"Please review this account notice carefully. This message was sent by an administrator."
"</div>"
"<div style=\"margin:0 0 18px; padding:18px; background:#241814; border:1px solid rgba(251,146,60,0.34); border-radius:18px; color:#ffe0ba;\">"
"<div style=\"font-size:11px; letter-spacing:0.12em; text-transform:uppercase; color:#fbbd7b; margin-bottom:8px;\">Reason</div>"
"<div style=\"font-size:18px; font-weight:800; line-height:1.5; white-space:pre-line;\">{{reason}}</div>"
"</div>"
"<div style=\"margin:0 0 18px; padding:18px; background:#101726; border:1px solid rgba(255,255,255,0.08); border-radius:18px; color:#dbe5ff; font-size:14px; line-height:1.8; white-space:pre-line;\">{{message}}</div>"
"<div style=\"margin:0; color:#9aa3b8; font-size:13px; line-height:1.7;\">If you need help or think this was sent in error, contact the site administrator.</div>"
+ _build_email_stat_grid(
[
_build_email_stat_card("Account", "{{username}}"),
_build_email_stat_card("Role", "{{role}}"),
_build_email_stat_card("Application", "{{app_name}}"),
_build_email_stat_card("Support", "{{how_it_works_url}}"),
]
)
+ _build_email_panel(
"Reason",
"<div style=\"font-size:18px; font-weight:800; line-height:1.6; white-space:pre-line;\">{{reason}}</div>",
variant="warning",
)
+ _build_email_panel(
"Administrator note",
"<div style=\"white-space:pre-line;\">{{message}}</div>",
variant="neutral",
)
+ _build_email_panel(
"What to do next",
_build_email_list(
[
"Review the note above and confirm you understand what needs to change.",
"If you need help, reply through your usual support path or contact an administrator.",
"Keep this email for reference until the matter is resolved.",
]
),
variant="neutral",
)
),
},
"banned": {
@@ -253,15 +391,35 @@ DEFAULT_TEMPLATES: Dict[str, Dict[str, str]] = {
"<div style=\"margin:0 0 18px; color:#e9ecf5; font-size:15px; line-height:1.7;\">"
"Your account access has changed. Review the details below."
"</div>"
"<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"margin:0 0 18px; border-collapse:collapse;\">"
"<tr>"
"<td style=\"padding:18px; background:#251418; border:1px solid rgba(239,68,68,0.32); border-radius:18px; color:#ffd0d0;\">"
"<div style=\"font-size:11px; letter-spacing:0.12em; text-transform:uppercase; color:#ff9b9b; margin-bottom:8px;\">Reason</div>"
"<div style=\"font-size:18px; font-weight:800; line-height:1.5; white-space:pre-line;\">{{reason}}</div>"
"</td>"
"</tr>"
"</table>"
"<div style=\"margin:0; padding:18px; background:#101726; border:1px solid rgba(255,255,255,0.08); border-radius:18px; color:#dbe5ff; font-size:14px; line-height:1.8; white-space:pre-line;\">{{message}}</div>"
+ _build_email_stat_grid(
[
_build_email_stat_card("Account", "{{username}}"),
_build_email_stat_card("Status", "Restricted"),
_build_email_stat_card("Application", "{{app_name}}"),
_build_email_stat_card("Guidance", "{{how_it_works_url}}"),
]
)
+ _build_email_panel(
"Reason",
"<div style=\"font-size:18px; font-weight:800; line-height:1.6; white-space:pre-line;\">{{reason}}</div>",
variant="danger",
)
+ _build_email_panel(
"Administrator note",
"<div style=\"white-space:pre-line;\">{{message}}</div>",
variant="neutral",
)
+ _build_email_panel(
"What this means",
_build_email_list(
[
"Your access has been removed or restricted across the linked services.",
"If you believe this is incorrect, contact the site administrator directly.",
"Do not rely on old links or cached sessions after this change.",
]
),
variant="neutral",
)
),
},
}
@@ -481,7 +639,7 @@ def build_invite_email_context(
invite.get("created_by") if invite else (user.get("username") if user else None),
"Admin",
),
"message": _normalize_display_text(message, ""),
"message": _normalize_display_text(message, "No additional note."),
"reason": _normalize_display_text(reason, "Not specified"),
"recipient_email": _normalize_display_text(resolved_recipient, "No email supplied"),
"role": _normalize_display_text(user.get("role") if user else None, "user"),
@@ -835,6 +993,13 @@ async def send_test_email(recipient_email: Optional[str] = None) -> Dict[str, st
application_url = _normalize_display_text(runtime.magent_application_url, "Not configured")
primary_url = application_url if application_url.lower().startswith(("http://", "https://")) else ""
smtp_target = f"{_normalize_display_text(runtime.magent_notify_email_smtp_host, 'Not configured')}:{int(runtime.magent_notify_email_smtp_port or 587)}"
security_mode = "SSL" if runtime.magent_notify_email_use_ssl else ("STARTTLS" if runtime.magent_notify_email_use_tls else "Plain SMTP")
auth_mode = "Authenticated" if (
_normalize_display_text(runtime.magent_notify_email_smtp_username)
and _normalize_display_text(runtime.magent_notify_email_smtp_password)
) else "No SMTP auth"
delivery_warning = smtp_email_delivery_warning()
subject = f"{env_settings.app_name} email test"
body_text = (
f"This is a test email from {env_settings.app_name}.\n\n"
@@ -852,21 +1017,36 @@ async def send_test_email(recipient_email: Optional[str] = None) -> Dict[str, st
"<div style=\"margin:0 0 18px; color:#e9ecf5; font-size:15px; line-height:1.7;\">"
"This is a live test email from Magent. If this renders correctly, the HTML template shell and SMTP handoff are both working."
"</div>"
"<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"border-collapse:separate; border-spacing:10px 10px; margin:0 0 18px;\">"
"<tr>"
"<td width=\"50%\" style=\"padding:16px; background:#151c2d; border:1px solid rgba(255,255,255,0.08); border-radius:16px; color:#e9ecf5;\">"
"<div style=\"font-size:11px; letter-spacing:0.12em; text-transform:uppercase; color:#9aa3b8; margin-bottom:8px;\">Build</div>"
f"<div style=\"font-size:22px; font-weight:800;\">{html.escape(BUILD_NUMBER)}</div>"
"</td>"
"<td width=\"50%\" style=\"padding:16px; background:#151c2d; border:1px solid rgba(255,255,255,0.08); border-radius:16px; color:#e9ecf5;\">"
"<div style=\"font-size:11px; letter-spacing:0.12em; text-transform:uppercase; color:#9aa3b8; margin-bottom:8px;\">Application URL</div>"
f"<div style=\"font-size:16px; font-weight:700; line-height:1.5;\">{html.escape(application_url)}</div>"
"</td>"
"</tr>"
"</table>"
"<div style=\"margin:0; padding:18px; background:#101726; border:1px dashed rgba(59,130,246,0.32); border-radius:18px; color:#dbe5ff; font-size:14px; line-height:1.7;\">"
"Use this test when changing SMTP settings, relay targets, or branding."
"</div>"
+ _build_email_stat_grid(
[
_build_email_stat_card("Recipient", resolved_email),
_build_email_stat_card("Build", BUILD_NUMBER),
_build_email_stat_card("SMTP target", smtp_target),
_build_email_stat_card("Security", security_mode, auth_mode),
_build_email_stat_card("Application URL", application_url),
_build_email_stat_card("Template shell", "Branded HTML", "Logo, gradient, action buttons"),
]
)
+ _build_email_panel(
"What this verifies",
_build_email_list(
[
"Magent can build the HTML template shell correctly.",
"The configured SMTP route accepts and relays the message.",
"Branding, links, and build metadata are rendering consistently.",
]
),
variant="brand",
)
+ _build_email_panel(
"Delivery notes",
(
f"<div style=\"white-space:pre-line;\">{html.escape(delivery_warning)}</div>"
if delivery_warning
else "Use this test when changing SMTP settings, relay targets, or branding."
),
variant="warning" if delivery_warning else "neutral",
)
),
primary_label="Open Magent" if primary_url else "",
primary_url=primary_url,
@@ -933,24 +1113,36 @@ async def send_password_reset_email(
f"<div style=\"margin:0 0 18px; color:#e9ecf5; font-size:15px; line-height:1.7;\">"
f"A password reset was requested for <strong>{html.escape(username)}</strong>."
"</div>"
"<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"border-collapse:separate; border-spacing:10px 10px; margin:0 0 18px;\">"
"<tr>"
"<td width=\"50%\" style=\"padding:16px; background:#151c2d; border:1px solid rgba(255,255,255,0.08); border-radius:16px; color:#e9ecf5;\">"
"<div style=\"font-size:11px; letter-spacing:0.12em; text-transform:uppercase; color:#9aa3b8; margin-bottom:8px;\">Account</div>"
f"<div style=\"font-size:22px; font-weight:800;\">{html.escape(username)}</div>"
"</td>"
"<td width=\"50%\" style=\"padding:16px; background:#151c2d; border:1px solid rgba(255,255,255,0.08); border-radius:16px; color:#e9ecf5;\">"
"<div style=\"font-size:11px; letter-spacing:0.12em; text-transform:uppercase; color:#9aa3b8; margin-bottom:8px;\">Expires</div>"
f"<div style=\"font-size:16px; font-weight:700; line-height:1.5;\">{html.escape(expires_at)}</div>"
"</td>"
"</tr>"
"</table>"
f"<div style=\"margin:0 0 18px; padding:18px; background:#101726; border:1px solid rgba(59,130,246,0.22); border-radius:18px; color:#dbe5ff; font-size:14px; line-height:1.7;\">"
f"This reset will update the password used for <strong>{html.escape(provider_label)}</strong>."
"</div>"
"<div style=\"margin:0; padding:18px; background:#1a1220; border:1px dashed rgba(255,107,43,0.38); border-radius:18px; color:#ffd3bf; font-size:14px; line-height:1.7;\">"
"If you did not request this reset, ignore this email. No changes will be applied until the reset link is opened and completed."
"</div>"
+ _build_email_stat_grid(
[
_build_email_stat_card("Account", username),
_build_email_stat_card("Expires", expires_at),
_build_email_stat_card("Credentials updated", provider_label),
_build_email_stat_card("Delivery target", resolved_email),
]
)
+ _build_email_panel(
"What will be updated",
f"This reset will update the password used for <strong>{html.escape(provider_label)}</strong>.",
variant="brand",
)
+ _build_email_panel(
"What happens next",
_build_email_list(
[
"Open the reset link and choose a new password.",
"Complete the form before the expiry time shown above.",
"Use the new password the next time you sign in.",
],
ordered=True,
),
variant="neutral",
)
+ _build_email_panel(
"Safety note",
"If you did not request this reset, ignore this email. No changes will be applied until the reset link is opened and completed.",
variant="warning",
)
),
primary_label="Reset password",
primary_url=reset_url,

View File

@@ -1,12 +1,12 @@
{
"name": "magent-frontend",
"version": "0303261629",
"version": "0303261702",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "magent-frontend",
"version": "0303261629",
"version": "0303261702",
"dependencies": {
"next": "16.1.6",
"react": "19.2.4",

View File

@@ -1,7 +1,7 @@
{
"name": "magent-frontend",
"private": true,
"version": "0303261629",
"version": "0303261702",
"scripts": {
"dev": "next dev",
"build": "next build",