heartwood every commit a ring

Redesign alert emails to match the warm-earth site palette

98a15471 by Isaac Bythewood · 24 days ago

Redesign alert emails to match the warm-earth site palette

Replaces the old Bootstrap-email scaffolding with a purpose-built dark
layout: mossy green + terracotta accents, monospace type, amber section
labels, and a CTA linking back to the property detail page. Down and
recovery variants now reduce to a one-line include that injects palette
and copy into the shared base.
modified properties/models.py
@@ -4,6 +4,7 @@ import timeimport uuidimport requestsfrom django.conf import settingsfrom django.contrib.auth import get_user_modelfrom django.core.mail import EmailMessagefrom django.db import models, transaction
@@ -112,7 +113,8 @@ class AlertsMixin:    def send_down_email(self):        subject = f"Status: {self.name} is down!"        message = render_to_string("emails/property_down.html", {"property": self})        context = {"property": self, "BASE_URL": settings.BASE_URL}        message = render_to_string("emails/property_down.html", context)        from_email = "noreply@bythewood.me"        to_emails = [self.user.email]        email = EmailMessage(subject, message, from_email, to_emails)
@@ -124,7 +126,8 @@ class AlertsMixin:    def send_recovery_email(self):        subject = f"Status: {self.name} is back up!"        message = render_to_string("emails/property_recovery.html", {"property": self})        context = {"property": self, "BASE_URL": settings.BASE_URL}        message = render_to_string("emails/property_recovery.html", context)        from_email = "noreply@bythewood.me"        to_emails = [self.user.email]        email = EmailMessage(subject, message, from_email, to_emails)
modified properties/templates/emails/property_down.html
@@ -1,15 +1 @@{% extends "emails/property_email_base.html" %}{% block header_class %}bg-danger text-white{% endblock %}{% block header_style %}background-color: #dc3545; color: #ffffff;{% endblock %}{% block header_title %}Property down{% endblock %}{% block alert_class %}alert-danger{% endblock %}{% block alert_style %}color: #66121a;{% endblock %}{% block alert_bg %}#fae3e5{% endblock %}{% block alert_message %}One of your properties just went down!{% endblock %}{% include "emails/property_email_base.html" with email_title="Property down" preheader=property.name|add:" is not responding" status_label="Down" event_title="Your property is down." event_copy="We've observed two consecutive failed checks in a row. Monitoring will continue in the background and you'll get another note the moment it recovers." accent="#c47055" accent_bright="#e38871" accent_tint="#201712" accent_border="#36231b" %}
modified properties/templates/emails/property_email_base.html
@@ -1,157 +1,143 @@<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html><!DOCTYPE html><html lang="en">  <head>    <meta charset="utf-8">    <meta http-equiv="x-ua-compatible" content="ie=edge">    <meta name="x-apple-disable-message-reformatting">    <meta name="viewport" content="width=device-width, initial-scale=1">    <meta name="x-apple-disable-message-reformatting">    <meta name="color-scheme" content="dark">    <meta name="supported-color-schemes" content="dark">    <meta name="format-detection" content="telephone=no, date=no, address=no, email=no">    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">    <title>{{ email_title }}</title>    <style type="text/css">      body,table,td{font-family:Helvetica,Arial,sans-serif !important}.ExternalClass{width:100%}.ExternalClass,.ExternalClass p,.ExternalClass span,.ExternalClass font,.ExternalClass td,.ExternalClass div{line-height:150%}a{text-decoration:none}*{color:inherit}a[x-apple-data-detectors],u+#body a,#MessageViewBody a{color:inherit;text-decoration:none;font-size:inherit;font-family:inherit;font-weight:inherit;line-height:inherit}img{-ms-interpolation-mode:bicubic}table:not([class^=s-]){font-family:Helvetica,Arial,sans-serif;mso-table-lspace:0pt;mso-table-rspace:0pt;border-spacing:0px;border-collapse:collapse}table:not([class^=s-]) td{border-spacing:0px;border-collapse:collapse}@media screen and (max-width: 600px){.w-full,.w-full>tbody>tr>td{width:100% !important}.p-3:not(table),.p-3:not(.btn)>tbody>tr>td,.p-3.btn td a{padding:12px !important}*[class*=s-lg-]>tbody>tr>td{font-size:0 !important;line-height:0 !important;height:0 !important}.s-4>tbody>tr>td{font-size:16px !important;line-height:16px !important;height:16px !important}.s-10>tbody>tr>td{font-size:40px !important;line-height:40px !important;height:40px !important}}      body { margin: 0 !important; padding: 0 !important; background: #0e0d0a !important; }      table { border-collapse: collapse; }      img { border: 0; display: block; }      a { color: #7db88c; text-decoration: none; }      a:hover { text-decoration: underline; }      @media (max-width: 620px) {        .shell { width: 100% !important; }        .px-pad { padding-left: 22px !important; padding-right: 22px !important; }        .kv-key { width: 100% !important; display: block !important; border-left: 0 !important; padding: 14px 0 0 0 !important; }        .kv-val { width: 100% !important; display: block !important; border-left: 0 !important; padding: 4px 0 14px 0 !important; }      }    </style>  </head>  <body class="bg-light" style="outline: 0; width: 100%; min-width: 100%; height: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: Helvetica, Arial, sans-serif; line-height: 24px; font-weight: normal; font-size: 16px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; color: #000000; margin: 0; padding: 0; border-width: 0;" bgcolor="#f7fafc">    <table class="bg-light body" valign="top" role="presentation" border="0" cellpadding="0" cellspacing="0" style="outline: 0; width: 100%; min-width: 100%; height: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: Helvetica, Arial, sans-serif; line-height: 24px; font-weight: normal; font-size: 16px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; color: #000000; margin: 0; padding: 0; border-width: 0;" bgcolor="#f7fafc">      <tbody>        <tr>          <td valign="top" style="line-height: 24px; font-size: 16px; margin: 0;" align="left" bgcolor="#f7fafc">            <table class="container" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">              <tbody>                <tr>                  <td align="center" style="line-height: 24px; font-size: 16px; margin: 0; padding: 0 16px;">                    <!--[if (gte mso 9)|(IE)]>                      <table align="center" role="presentation">                        <tbody>                          <tr>                            <td width="600">                    <![endif]-->                    <table align="center" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%; max-width: 600px; margin: 0 auto;">                      <tbody>                        <tr>                          <td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">                            <table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">                              <tbody>                                <tr>                                  <td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">                                    &#160;                                  </td>                                </tr>                              </tbody>                            </table>                            <table class="card  rounded-none" role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-radius: 0px; border-collapse: separate !important; width: 100%; overflow: hidden; border: 1px solid #e2e8f0;" bgcolor="#ffffff">                              <tbody>                                <tr>                                  <td style="line-height: 24px; font-size: 16px; width: 100%; border-radius: 0px; margin: 0;" align="left" bgcolor="#ffffff">                                    <table class="card-body" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">                                      <tbody>                                        <tr>                                          <td style="line-height: 24px; font-size: 16px; width: 100%; margin: 0; padding: 20px;" align="left">                                            <h1 class="{% block header_class %}{% endblock %}" style="{% block header_style %}{% endblock %} padding-top: 0; padding-bottom: 0; font-weight: 500; vertical-align: baseline; font-size: 36px; line-height: 43.2px; margin: 0;" align="left">                                              <table class="p-3" role="presentation" border="0" cellpadding="0" cellspacing="0">                                                <tbody>                                                  <tr>                                                    <td style="line-height: 24px; font-size: 16px; margin: 0; padding: 12px;" align="left">                                                      <strong class="h2" style="padding-top: 0; padding-bottom: 0; font-weight: 500; text-align: left; vertical-align: baseline; font-size: 32px; line-height: 38.4px; margin: 0;">{% block header_title %}{% endblock %}</strong>                                                    </td>                                                  </tr>                                                </tbody>                                              </table>                                            </h1>                                            <table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">                                              <tbody>                                                <tr>                                                  <td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">                                                    &#160;                                                  </td>                                                </tr>                                              </tbody>                                            </table>                                            <table class="alert {% block alert_class %}{% endblock %} rounded-none" role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate !important; width: 100%; border-radius: 0px; border-width: 0;">                                              <tbody>                                                <tr>                                                  <td style="line-height: 24px; font-size: 16px; border-radius: 0px; {% block alert_style %}{% endblock %} margin: 0; padding: 12px 20px; border: 1px solid transparent;" align="left" bgcolor="{% block alert_bg %}{% endblock %}">                                                    <div>                                                      {% block alert_message %}{% endblock %}                                                    </div>                                                  </td>                                                </tr>                                              </tbody>                                            </table>                                            <table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">                                              <tbody>                                                <tr>                                                  <td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">                                                    &#160;                                                  </td>                                                </tr>                                              </tbody>                                            </table>                                            <div class="space-y-3">                                              <table class="table" border="0" cellpadding="0" cellspacing="0" style="width: 100%; max-width: 100%;">                                                <tbody>                                                  <tr>                                                    <td style="line-height: 24px; font-size: 16px; border-top-width: 1px; border-top-color: #e2e8f0; border-top-style: solid; margin: 0; padding: 12px;" align="left" valign="top">                                                      <strong>name</strong>                                                    </td>                                                    <td style="line-height: 24px; font-size: 16px; border-top-width: 1px; border-top-color: #e2e8f0; border-top-style: solid; margin: 0; padding: 12px;" align="left" valign="top">{{ property.name }}</td>                                                  </tr>                                                  <tr>                                                    <td style="line-height: 24px; font-size: 16px; border-top-width: 1px; border-top-color: #e2e8f0; border-top-style: solid; margin: 0; padding: 12px;" align="left" valign="top">                                                      <strong>url</strong>                                                    </td>                                                    <td style="line-height: 24px; font-size: 16px; border-top-width: 1px; border-top-color: #e2e8f0; border-top-style: solid; margin: 0; padding: 12px;" align="left" valign="top">                                                      <a href="{{ property.url }}" target="_blank" style="color: #0d6efd;">{{ property.url }}</a>                                                    </td>                                                  </tr>                                                  <tr>                                                    <td style="line-height: 24px; font-size: 16px; border-top-width: 1px; border-top-color: #e2e8f0; border-top-style: solid; margin: 0; padding: 12px;" align="left" valign="top">                                                      <strong>status</strong>                                                    </td>                                                    <td style="line-height: 24px; font-size: 16px; border-top-width: 1px; border-top-color: #e2e8f0; border-top-style: solid; margin: 0; padding: 12px;" align="left" valign="top">{{ property.current_status }}</td>                                                  </tr>                                                  <tr>                                                    <td style="line-height: 24px; font-size: 16px; border-top-width: 1px; border-top-color: #e2e8f0; border-top-style: solid; margin: 0; padding: 12px;" align="left" valign="top">                                                      <strong>avg. time</strong>                                                    </td>                                                    <td style="line-height: 24px; font-size: 16px; border-top-width: 1px; border-top-color: #e2e8f0; border-top-style: solid; margin: 0; padding: 12px;" align="left" valign="top">{{ property.avg_response_time }}</td>                                                  </tr>                                                </tbody>                                              </table>                                            </div>                                          </td>                                        </tr>                                      </tbody>                                    </table>                                  </td>                                </tr>                              </tbody>                            </table>                            <table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">                              <tbody>                                <tr>                                  <td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">                                    &#160;                                  </td>                                </tr>                              </tbody>                            </table>                          </td>                        </tr>                      </tbody>                    </table>                    <!--[if (gte mso 9)|(IE)]>  <body style="margin: 0; padding: 0; background-color: #0e0d0a; color: #ddd7cd; font-family: ui-monospace, 'Cascadia Code', 'SF Mono', Menlo, Consolas, monospace; font-size: 14px; line-height: 1.65;">    <div style="display: none; max-height: 0; overflow: hidden; mso-hide: all; font-size: 1px; line-height: 1px; color: #0e0d0a;">{{ preheader }}</div>    <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="background-color: #0e0d0a;">      <tr>        <td align="center" style="padding: 40px 16px;">          <table role="presentation" cellpadding="0" cellspacing="0" border="0" class="shell" width="560" style="width: 100%; max-width: 560px;">            <tr>              <td class="px-pad" style="padding: 0 0 18px 0;">                <table role="presentation" cellpadding="0" cellspacing="0" border="0">                  <tr>                    <td style="vertical-align: middle; padding-right: 10px; line-height: 0;">                      <span style="display: inline-block; width: 8px; height: 8px; background-color: #6b9e78; border-radius: 50%; box-shadow: 0 0 0 3px rgba(107,158,120,0.18);"></span>                    </td>                    <td style="vertical-align: middle; color: #ede8e0; font-family: ui-monospace, 'Cascadia Code', 'SF Mono', Menlo, Consolas, monospace; font-size: 12px; letter-spacing: 0.18em; text-transform: uppercase; font-weight: 700;">                      // Status                    </td>                  </tr>                </table>              </td>            </tr>          </table>          <table role="presentation" cellpadding="0" cellspacing="0" border="0" class="shell" width="560" style="width: 100%; max-width: 560px; background-color: #13120e; border: 1px solid {{ accent_border }};">            <tr>              <td style="height: 3px; background-color: {{ accent }}; line-height: 3px; font-size: 0;">&nbsp;</td>            </tr>            <tr>              <td class="px-pad" style="padding: 30px 32px 6px 32px;">                <table role="presentation" cellpadding="0" cellspacing="0" border="0">                  <tr>                    <td style="vertical-align: middle; padding-right: 8px; line-height: 0;">                      <span style="display: inline-block; width: 7px; height: 7px; background-color: {{ accent }}; border-radius: 50%;"></span>                    </td>                    <td style="vertical-align: middle; color: {{ accent_bright }}; font-family: ui-monospace, 'Cascadia Code', 'SF Mono', Menlo, Consolas, monospace; font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; font-weight: 700;">                      {{ status_label }}                    </td>                  </tr>                </table>              </td>            </tr>            <tr>              <td class="px-pad" style="padding: 14px 32px 4px 32px;">                <h1 style="margin: 0; color: #ede8e0; font-family: ui-monospace, 'Cascadia Code', 'SF Mono', Menlo, Consolas, monospace; font-size: 26px; line-height: 1.25; font-weight: 700; letter-spacing: -0.01em;">                  {{ event_title }}                </h1>              </td>            </tr>            <tr>              <td class="px-pad" style="padding: 14px 32px 26px 32px; color: #a09890; font-size: 14px; line-height: 1.75;">                {{ event_copy }}              </td>            </tr>            <tr>              <td class="px-pad" style="padding: 0 32px 6px 32px;">                <div style="font-family: ui-monospace, 'Cascadia Code', 'SF Mono', Menlo, Consolas, monospace; font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; color: #c9a84c; padding: 0 0 10px 0; font-weight: 700;">                  / Property                </div>                <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="border: 1px solid rgba(107,158,120,0.12); background: rgba(9,8,6,0.5);">                  <tr>                    <td class="kv-key" width="32%" style="padding: 12px 14px; color: #665f56; font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 700; vertical-align: top; font-family: ui-monospace, 'Cascadia Code', 'SF Mono', Menlo, Consolas, monospace;">Name</td>                    <td class="kv-val" style="padding: 12px 14px; color: #ede8e0; font-size: 13px; font-family: ui-monospace, 'Cascadia Code', 'SF Mono', Menlo, Consolas, monospace; border-left: 1px solid rgba(107,158,120,0.08); vertical-align: top; word-break: break-word;">{{ property.name }}</td>                  </tr>                  <tr>                    <td class="kv-key" style="padding: 12px 14px; color: #665f56; font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 700; border-top: 1px solid rgba(107,158,120,0.08); vertical-align: top; font-family: ui-monospace, 'Cascadia Code', 'SF Mono', Menlo, Consolas, monospace;">URL</td>                    <td class="kv-val" style="padding: 12px 14px; font-size: 13px; font-family: ui-monospace, 'Cascadia Code', 'SF Mono', Menlo, Consolas, monospace; border-top: 1px solid rgba(107,158,120,0.08); border-left: 1px solid rgba(107,158,120,0.08); vertical-align: top; word-break: break-all;">                      <a href="{{ property.url }}" style="color: #7db88c; text-decoration: none;">{{ property.url }}</a>                    </td>                  </tr>                  <tr>                    <td class="kv-key" style="padding: 12px 14px; color: #665f56; font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 700; border-top: 1px solid rgba(107,158,120,0.08); vertical-align: top; font-family: ui-monospace, 'Cascadia Code', 'SF Mono', Menlo, Consolas, monospace;">Status</td>                    <td class="kv-val" style="padding: 12px 14px; font-size: 13px; font-family: ui-monospace, 'Cascadia Code', 'SF Mono', Menlo, Consolas, monospace; border-top: 1px solid rgba(107,158,120,0.08); border-left: 1px solid rgba(107,158,120,0.08); vertical-align: top;">                      <span style="display: inline-block; background-color: {{ accent_tint }}; color: {{ accent_bright }}; border: 1px solid {{ accent_border }}; padding: 3px 9px; font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 700;">{{ property.current_status }}</span>                    </td>                  </tr>                  <tr>                    <td class="kv-key" style="padding: 12px 14px; color: #665f56; font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 700; border-top: 1px solid rgba(107,158,120,0.08); vertical-align: top; font-family: ui-monospace, 'Cascadia Code', 'SF Mono', Menlo, Consolas, monospace;">Avg time</td>                    <td class="kv-val" style="padding: 12px 14px; color: #ede8e0; font-size: 13px; font-family: ui-monospace, 'Cascadia Code', 'SF Mono', Menlo, Consolas, monospace; border-top: 1px solid rgba(107,158,120,0.08); border-left: 1px solid rgba(107,158,120,0.08); vertical-align: top;">{{ property.avg_response_time }} ms</td>                  </tr>                </table>              </td>            </tr>            <tr>              <td class="px-pad" style="padding: 26px 32px 32px 32px;">                <table role="presentation" cellpadding="0" cellspacing="0" border="0">                  <tr>                    <td style="background-color: {{ accent_tint }}; border: 1px solid {{ accent_border }};">                      <a href="{{ BASE_URL }}/properties/{{ property.id }}/" style="display: inline-block; padding: 12px 22px; color: {{ accent_bright }}; font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 700; text-decoration: none; font-family: ui-monospace, 'Cascadia Code', 'SF Mono', Menlo, Consolas, monospace;">                        View property &nbsp;&rarr;                      </a>                    </td>                  </tr>                </tbody>              </table>                    <![endif]-->                  </td>                </tr>              </tbody>            </table>          </td>        </tr>      </tbody>                </table>              </td>            </tr>          </table>          <table role="presentation" cellpadding="0" cellspacing="0" border="0" class="shell" width="560" style="width: 100%; max-width: 560px;">            <tr>              <td class="px-pad" style="padding: 24px 0 0 0; color: #4a443c; font-size: 11px; line-height: 1.8; letter-spacing: 0.02em;">                Self-hosted monitoring &middot; HTTP checks every three minutes<br>                Alerts fire only on state transitions. Manage notifications from your <a href="{{ BASE_URL }}/accounts/profile/" style="color: #665f56; text-decoration: underline;">profile</a>.              </td>            </tr>          </table>        </td>      </tr>    </table>  </body></html>
modified properties/templates/emails/property_recovery.html
@@ -1,15 +1 @@{% extends "emails/property_email_base.html" %}{% block header_class %}bg-success text-white{% endblock %}{% block header_style %}background-color: #28a745; color: #ffffff;{% endblock %}{% block header_title %}Property recovered{% endblock %}{% block alert_class %}alert-success{% endblock %}{% block alert_style %}color: #1e4d20;{% endblock %}{% block alert_bg %}#e6ffed{% endblock %}{% block alert_message %}Good news! Your property is back online and responding normally.{% endblock %}{% include "emails/property_email_base.html" with email_title="Property recovered" preheader=property.name|add:" is back online" status_label="Recovered" event_title="Your property is back online." event_copy="The latest check returned a healthy response. Everything looks normal again — we'll keep monitoring and alert you if anything changes." accent="#6b9e78" accent_bright="#7db88c" accent_tint="#191e17" accent_border="#222d22" %}