Style newsletters and system e-mails like www.eom.dev

Catppuccin Mocha campaign template (default) and archive variant, kept
in sync in the database; system e-mail header/footer overridden via
--static-dir from a ConfigMap. PNG logo for mail clients. Tested in
Mailpit (desktop, phone width; 87% CSS support, rest inlined).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-06 21:36:39 -05:00
co-authored by Claude Opus 5.5
parent baa2d4b521
commit a74d580b0a
9 changed files with 226 additions and 4 deletions
+53
View File
@@ -0,0 +1,53 @@
#jinja2:variable_start_string:'[[', variable_end_string:']]', block_start_string:'[%', block_end_string:'%]'
{{ define "header" }}
<!doctype html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="color-scheme" content="dark">
<meta name="supported-color-schemes" content="dark">
<base target="_blank">
<!-- eom.dev system e-mails (opt-in, password reset, ...). Managed by ansible-role-listmonk. -->
<style>
body { margin: 0; padding: 0; background: #181825; color: #cdd6f4; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.65; }
.wrap { max-width: 600px; margin: 0 auto; background: #1e1e2e; border: 1px solid #3c3d56; border-radius: 20px; padding: 24px 36px 20px; color: #cdd6f4; }
.header { border-bottom: 1px solid #3c3d56; padding-bottom: 20px; margin-bottom: 24px; }
.header img { display: block; border: 0; width: 240px; height: 72px; }
.gutter { padding: 16px; }
h1, h2, h3 { color: #cdd6f4; line-height: 1.3; }
h2 { font-size: 21px; font-weight: 750; margin: 0 0 12px; }
p { margin: 0 0 16px; }
a { color: #cba6f7; }
ul { padding-left: 22px; }
table td { color: #cdd6f4; border-bottom: 1px solid #3c3d56; padding: 6px 4px; }
.button { display: inline-block; background: #cba6f7; color: #11111b !important; text-decoration: none; font-weight: 700; padding: 12px 22px; border-radius: 10px; }
.footer { text-align: center; font-size: 13px; color: #7f849c; }
.footer a { color: #a6adc8; }
img { max-width: 100%; }
@media screen and (max-width: 620px) {
.wrap { border-radius: 0; border-left: 0; border-right: 0; padding: 20px; }
.gutter { padding: 6px; }
}
</style>
</head>
<body style="margin:0;padding:0;background:#181825;" bgcolor="#181825">
<div class="gutter">&nbsp;</div>
<div class="wrap" style="background:#1e1e2e;border:1px solid #3c3d56;border-radius:20px;color:#cdd6f4;">
<div class="header">
<a href="[[ listmonk_email_home_url ]]"><img src="[[ listmonk_email_logo_url ]]" width="240" height="72" alt="eom.dev"></a>
</div>
{{ end }}
{{ define "footer" }}
</div>
<div class="footer">
<p><a href="[[ listmonk_email_home_url ]]">eom.dev</a> &nbsp;·&nbsp; <a href="[[ listmonk_email_members_url ]]">Membership</a></p>
[% if listmonk_email_postal_address | length > 0 %]
<p>[[ listmonk_email_postal_address | e ]]</p>
[% endif %]
</div>
<div class="gutter">&nbsp;</div>
</body>
</html>
{{ end }}
+89
View File
@@ -0,0 +1,89 @@
#jinja2:variable_start_string:'[[', variable_end_string:']]', block_start_string:'[%', block_end_string:'%]'
<!doctype html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="color-scheme" content="dark">
<meta name="supported-color-schemes" content="dark">
<base target="_blank">
<title>{{ .Campaign.Subject }}</title>
<!-- eom.dev newsletter: Catppuccin Mocha, as on www.eom.dev. Managed by ansible-role-listmonk. -->
<style>
body { margin: 0; padding: 0; background: #181825; -webkit-text-size-adjust: 100%; }
.content { color: #cdd6f4; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.65; }
.content h1 { font-size: 27px; line-height: 1.25; font-weight: 800; letter-spacing: -0.4px; margin: 0 0 16px; color: #cdd6f4; }
.content h2 { font-size: 21px; line-height: 1.3; font-weight: 750; margin: 28px 0 12px; color: #cdd6f4; }
.content h3 { font-size: 17px; line-height: 1.35; font-weight: 700; margin: 24px 0 8px; color: #b4befe; }
.content h1:first-child, .content h2:first-child { margin-top: 0; }
.content p { margin: 0 0 16px; }
.content a { color: #cba6f7; text-decoration: underline; }
.content strong { color: #cdd6f4; }
.content ul, .content ol { margin: 0 0 16px; padding-left: 22px; }
.content li { margin: 0 0 6px; }
.content blockquote { margin: 0 0 16px; padding: 2px 0 2px 16px; border-left: 3px solid #cba6f7; color: #a6adc8; }
.content code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 14px; background: #181825; border: 1px solid #3c3d56; border-radius: 5px; padding: 1px 5px; }
.content pre { margin: 0 0 16px; padding: 14px 16px; background: #181825; border: 1px solid #3c3d56; border-radius: 10px; overflow-x: auto; }
.content pre code { border: 0; padding: 0; background: none; }
.content img { max-width: 100%; height: auto; border-radius: 12px; }
.content hr { border: 0; border-top: 1px solid #3c3d56; margin: 28px 0; }
.content table { border-collapse: collapse; margin: 0 0 16px; }
.content th, .content td { border: 1px solid #3c3d56; padding: 6px 10px; }
a.button, .content a.button { display: inline-block; background: #cba6f7; color: #11111b !important; text-decoration: none; font-weight: 700; padding: 12px 22px; border-radius: 10px; }
.footer a { color: #a6adc8; text-decoration: underline; }
@media screen and (max-width: 620px) {
.outer { padding: 0 !important; }
.card { border-radius: 0 !important; border-left: 0 !important; border-right: 0 !important; }
.pad { padding-left: 20px !important; padding-right: 20px !important; }
}
</style>
</head>
<body style="margin:0;padding:0;background:#181825;" bgcolor="#181825">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#181825" style="background:#181825;">
<tr>
<td class="outer" align="center" style="padding:32px 12px;">
<table role="presentation" class="card" width="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#1e1e2e" style="max-width:600px;background:#1e1e2e;border:1px solid #3c3d56;border-radius:20px;">
<tr>
<td class="pad" style="padding:24px 36px 20px;">
<a href="[[ listmonk_email_home_url ]]" style="text-decoration:none;"><img src="[[ listmonk_email_logo_url ]]" width="240" height="72" alt="eom.dev" style="display:block;border:0;outline:none;width:240px;height:72px;"></a>
</td>
</tr>
<tr>
<td class="pad" style="padding:0 36px;"><div style="height:1px;line-height:1px;font-size:1px;background:#3c3d56;">&nbsp;</div></td>
</tr>
<tr>
<td class="pad content" style="padding:28px 36px 16px;color:#cdd6f4;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;font-size:16px;line-height:1.65;">
{{ template "content" . }}
</td>
</tr>
</table>
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="max-width:600px;">
<tr>
<td class="footer pad" align="center" style="padding:20px 24px 8px;color:#7f849c;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;font-size:13px;line-height:1.6;">
<p style="margin:0 0 8px;">
<a href="[[ listmonk_email_home_url ]]" style="color:#a6adc8;">eom.dev</a> &nbsp;·&nbsp;
<a href="[[ listmonk_email_members_url ]]" style="color:#a6adc8;">Membership</a>
[% if not archive %]
&nbsp;·&nbsp; <a href="{{ MessageURL }}" style="color:#a6adc8;">View in browser</a>
[% endif %]
</p>
[% if not archive %]
<p style="margin:0 0 8px;">You're receiving this because you subscribed to the eom.dev newsletter.<br>
<a href="{{ ManageURL }}" style="color:#a6adc8;">Manage preferences</a> &nbsp;·&nbsp;
<a href="{{ UnsubscribeURL }}" style="color:#a6adc8;">Unsubscribe</a>
</p>
[% endif %]
[% if listmonk_email_postal_address | length > 0 %]
<p style="margin:0 0 8px;">[[ listmonk_email_postal_address | e ]]</p>
[% endif %]
</td>
</tr>
</table>
[% if not archive %]
{{ TrackView }}
[% endif %]
</td>
</tr>
</table>
</body>
</html>