Send branded HTML emails and harden SMTP2GO webhooks.
Deploy Beta / unit-tests (push) Successful in 9s
Deploy Beta / docker (push) Successful in 15s
Deploy Beta / deploy-beta (push) Successful in 1m36s

Fix campaign stuck on sending under async queue, and stop UUID ValidationError when SMTP2GO tests send "Headers Unavailable".
This commit is contained in:
2026-08-09 06:37:20 -05:00
parent 617bda3e8b
commit d830f07757
16 changed files with 558 additions and 63 deletions
+60
View File
@@ -0,0 +1,60 @@
"""Shared context for branded HTML/text emails (public site palette)."""
from __future__ import annotations
import html
import re
from django.conf import settings
from django.contrib.staticfiles.storage import staticfiles_storage
_URL_RE = re.compile(r"(https?://[^\s<]+)")
def email_brand_context(**extra):
site_url = (getattr(settings, "PUBLIC_SITE_URL", None) or "").rstrip("/")
if not site_url:
site_url = "https://mkdrealtor.com"
logo_path = staticfiles_storage.url("brand/exit_logo.png")
if logo_path.startswith("http://") or logo_path.startswith("https://"):
logo_url = logo_path
else:
logo_url = f"{site_url}{logo_path}"
brand_name = getattr(settings, "SITE_NAME", None) or "Monica Dhillon"
brand_legal = getattr(settings, "CREDIT_NAME", None) or brand_name
tagline = getattr(settings, "SITE_TAGLINE", None) or ""
host_label = site_url.replace("https://", "").replace("http://", "")
return {
"site_url": site_url,
"logo_url": logo_url,
"brand_name": brand_name,
"brand_legal": brand_legal,
"brand_tagline": tagline,
"host_label": host_label,
**extra,
}
def plain_text_to_email_html(text: str) -> str:
"""Escape plain text and turn paragraphs / URLs into simple HTML."""
raw = (text or "").replace("\r\n", "\n").strip()
if not raw:
return ""
blocks: list[str] = []
for para in re.split(r"\n\s*\n", raw):
lines = [html.escape(line) for line in para.split("\n")]
joined = "<br>\n".join(lines)
joined = _URL_RE.sub(
r'<a href="\1" style="color:#00626c;text-decoration:underline;">\1</a>',
joined,
)
blocks.append(
f'<p style="margin:0 0 16px;color:#212121;font-size:15px;'
f'line-height:1.6;">{joined}</p>'
)
return "\n".join(blocks)
+15 -11
View File
@@ -3,10 +3,12 @@
import logging
from django.conf import settings
from django.core.mail import EmailMessage
from django.core.mail import EmailMultiAlternatives
from django.template.loader import get_template
from django.urls import reverse
from leads.models import Lead
from public.email_branding import email_brand_context
logger = logging.getLogger(__name__)
@@ -49,23 +51,25 @@ def notify_admins_of_contact_form(lead: Lead) -> bool:
]
address = "\n".join(bit for bit in address_bits if bit) or "(none)"
body = (
f"New contact form inquiry from {name}.\n\n"
f"Name: {name}\n"
f"Email: {email}\n"
f"Phone: {phone}\n"
f"Address:\n{address}\n\n"
f"Message:\n{message}\n\n"
f"View in portal: {portal_url}\n"
ctx = email_brand_context(
name=name,
email=email,
phone=phone,
address=address,
message=message,
portal_url=portal_url,
)
text_content = get_template("emails/contact_email.txt").render(ctx)
html_content = get_template("emails/contact_email.html").render(ctx)
mail = EmailMessage(
mail = EmailMultiAlternatives(
subject=f"New contact form inquiry from {name}",
body=body,
body=text_content,
from_email=settings.DEFAULT_FROM_EMAIL,
to=[to_email],
reply_to=[contact.email] if contact.email else None,
)
mail.attach_alternative(html_content, "text/html")
try:
mail.send(fail_silently=False)
except Exception:
@@ -0,0 +1,113 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light">
<meta name="supported-color-schemes" content="light">
<title>{% block title %}{{ brand_name|default:"Monica Dhillon" }}{% endblock %}</title>
<!--[if mso]>
<style type="text/css">
body, table, td { font-family: Arial, Helvetica, sans-serif !important; }
</style>
<![endif]-->
<style type="text/css">
body, table, td, a {
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
table, td {
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
img {
border: 0;
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
-ms-interpolation-mode: bicubic;
}
body {
margin: 0 !important;
padding: 0 !important;
width: 100% !important;
background-color: #f4f7f7;
color: #212121;
font-family: "Work Sans", Poppins, -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
}
a { color: #00626c; }
.email-btn {
display: inline-block;
padding: 12px 24px;
background-color: #00626c;
color: #ffffff !important;
text-decoration: none;
border-radius: 4px;
font-weight: 600;
font-size: 14px;
letter-spacing: 0.3px;
}
.muted { color: #6b7280; font-size: 13px; line-height: 1.5; }
.field-label { color: #6b7280; font-size: 12px; text-transform: uppercase; letter-spacing: 0.6px; margin: 0 0 4px; }
.field-value { color: #212121; font-size: 15px; margin: 0 0 16px; line-height: 1.5; }
</style>
</head>
<body style="margin:0;padding:0;background-color:#f4f7f7;">
{% block preheader %}{% endblock %}
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background-color:#f4f7f7;">
<tr>
<td align="center" style="padding:32px 16px;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="max-width:600px;background-color:#ffffff;border:1px solid #d9e3e4;">
<tr>
<td align="center" style="padding:28px 24px 20px;border-bottom:1px solid #d9e3e4;">
{% if logo_url %}
<a href="{{ site_url|default:'https://mkdrealtor.com' }}" style="text-decoration:none;">
<img src="{{ logo_url }}" alt="{{ brand_name|default:'Monica Dhillon' }} · EXIT Realty" width="160" style="display:block;width:160px;max-width:70%;height:auto;">
</a>
{% else %}
<p style="margin:0;font-size:20px;font-weight:700;letter-spacing:0.5px;color:#00626c;">
{{ brand_name|default:"Monica Dhillon" }}
</p>
{% endif %}
{% if brand_name %}
<p style="margin:12px 0 0;font-size:15px;font-weight:600;color:#212121;">{{ brand_name }}</p>
{% endif %}
{% if brand_tagline %}
<p style="margin:4px 0 0;font-size:12px;color:#6b7280;">{{ brand_tagline }}</p>
{% endif %}
{% block header_extra %}{% endblock %}
</td>
</tr>
<tr>
<td style="height:3px;line-height:3px;font-size:0;background-color:#00626c;">&nbsp;</td>
</tr>
<tr>
<td style="padding:28px 28px 8px;color:#212121;font-size:15px;line-height:1.6;font-family:'Work Sans',Poppins,-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;">
{% block content %}{% endblock %}
</td>
</tr>
<tr>
<td style="padding:8px 28px 28px;color:#6b7280;font-size:12px;line-height:1.5;text-align:center;border-top:1px solid #d9e3e4;font-family:'Work Sans',Poppins,-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;">
{% block footer %}
<p style="margin:16px 0 8px;color:#6b7280;">
{% block footer_note %}{% endblock %}
</p>
<p style="margin:0 0 4px;color:#6b7280;">
&copy; {% now "Y" %} {{ brand_name|default:"Monica Dhillon" }}. All rights reserved.
</p>
<p style="margin:0;color:#6b7280;">
<a href="{{ site_url|default:'https://mkdrealtor.com' }}" style="color:#00626c;text-decoration:none;">{{ host_label|default:"mkdrealtor.com" }}</a>
{% if brand_tagline %}
&nbsp;·&nbsp; {{ brand_tagline }}
{% endif %}
</p>
{% endblock %}
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
@@ -0,0 +1,40 @@
{% extends "emails/base_email.html" %}
{% block title %}{{ subject }}{% endblock %}
{% block content %}
<p style="margin:0 0 16px;color:#212121;">Your {{ channel_display }} campaign has finished sending.</p>
<p style="margin:0 0 8px;font-size:18px;font-weight:600;color:#00626c;">{{ campaign_name }}</p>
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="margin:20px 0;">
<tr>
<td style="padding:8px 0;border-bottom:1px solid #d9e3e4;color:#6b7280;font-size:13px;">Recipients</td>
<td align="right" style="padding:8px 0;border-bottom:1px solid #d9e3e4;color:#212121;font-size:15px;font-weight:600;">{{ total }}</td>
</tr>
<tr>
<td style="padding:8px 0;border-bottom:1px solid #d9e3e4;color:#6b7280;font-size:13px;">Sent</td>
<td align="right" style="padding:8px 0;border-bottom:1px solid #d9e3e4;color:#212121;font-size:15px;font-weight:600;">{{ sent }}</td>
</tr>
<tr>
<td style="padding:8px 0;border-bottom:1px solid #d9e3e4;color:#6b7280;font-size:13px;">Delivered</td>
<td align="right" style="padding:8px 0;border-bottom:1px solid #d9e3e4;color:#212121;font-size:15px;font-weight:600;">{{ delivered }}</td>
</tr>
<tr>
<td style="padding:8px 0;border-bottom:1px solid #d9e3e4;color:#6b7280;font-size:13px;">Failed / bounced</td>
<td align="right" style="padding:8px 0;border-bottom:1px solid #d9e3e4;color:#212121;font-size:15px;font-weight:600;">{{ failed }}</td>
</tr>
<tr>
<td style="padding:8px 0;color:#6b7280;font-size:13px;">Suppressed</td>
<td align="right" style="padding:8px 0;color:#212121;font-size:15px;font-weight:600;">{{ suppressed }}</td>
</tr>
</table>
{% if report_url %}
<p style="margin:24px 0 0;">
<a class="email-btn" href="{{ report_url }}" style="display:inline-block;padding:12px 24px;background-color:#00626c;color:#ffffff !important;text-decoration:none;border-radius:4px;font-weight:600;font-size:14px;">Open campaign report</a>
</p>
{% endif %}
{% endblock %}
{% block footer_note %}Campaign summary from {{ brand_name|default:"Monica Dhillon" }}.{% endblock %}
@@ -0,0 +1,16 @@
Campaign sent: {{ campaign_name }}
Your {{ channel_display }} campaign "{{ campaign_name }}" has finished sending.
Recipients: {{ total }}
Sent: {{ sent }}
Delivered: {{ delivered }}
Failed / bounced: {{ failed }}
Suppressed: {{ suppressed }}
{% if report_url %}Report: {{ report_url }}
{% endif %}
{{ brand_name|default:"Monica Dhillon" }}
{{ site_url|default:"https://mkdrealtor.com" }}
{% if brand_tagline %}{{ brand_tagline }}{% endif %}
@@ -0,0 +1,33 @@
{% extends "emails/base_email.html" %}
{% block title %}New Contact Request{% endblock %}
{% block content %}
<p style="margin:0 0 16px;color:#212121;">Hello,</p>
<p style="margin:0 0 24px;color:#212121;">A new contact request was submitted on the site.</p>
<p class="field-label" style="color:#6b7280;font-size:12px;text-transform:uppercase;letter-spacing:0.6px;margin:0 0 4px;">Name</p>
<p class="field-value" style="color:#212121;font-size:15px;margin:0 0 16px;"><strong>{{ name }}</strong></p>
<p class="field-label" style="color:#6b7280;font-size:12px;text-transform:uppercase;letter-spacing:0.6px;margin:0 0 4px;">Email</p>
<p class="field-value" style="color:#212121;font-size:15px;margin:0 0 16px;">
<a href="mailto:{{ email }}" style="color:#00626c;text-decoration:none;">{{ email }}</a>
</p>
<p class="field-label" style="color:#6b7280;font-size:12px;text-transform:uppercase;letter-spacing:0.6px;margin:0 0 4px;">Phone</p>
<p class="field-value" style="color:#212121;font-size:15px;margin:0 0 16px;">{{ phone }}</p>
<p class="field-label" style="color:#6b7280;font-size:12px;text-transform:uppercase;letter-spacing:0.6px;margin:0 0 4px;">Address</p>
<p class="field-value" style="color:#212121;font-size:15px;margin:0 0 16px;white-space:pre-wrap;">{{ address }}</p>
<p class="field-label" style="color:#6b7280;font-size:12px;text-transform:uppercase;letter-spacing:0.6px;margin:0 0 4px;">Message</p>
<p class="field-value" style="color:#212121;font-size:15px;margin:0 0 16px;white-space:pre-wrap;">{{ message }}</p>
{% if portal_url %}
<p style="margin:24px 0 0;">
<a class="email-btn" href="{{ portal_url }}" style="display:inline-block;padding:12px 24px;background-color:#00626c;color:#ffffff !important;text-decoration:none;border-radius:4px;font-weight:600;font-size:14px;">View in portal</a>
</p>
{% endif %}
{% endblock %}
{% block footer_note %}This is an automated message from {{ brand_name|default:"Monica Dhillon" }}.{% endblock %}
@@ -0,0 +1,17 @@
New contact form inquiry — {{ brand_name|default:"Monica Dhillon" }}
Name: {{ name }}
Email: {{ email }}
Phone: {{ phone }}
Address:
{{ address }}
Message:
{{ message }}
{% if portal_url %}View in portal: {{ portal_url }}
{% endif %}
{{ brand_name|default:"Monica Dhillon" }}
{{ site_url|default:"https://mkdrealtor.com" }}
{% if brand_tagline %}{{ brand_tagline }}{% endif %}
@@ -0,0 +1,25 @@
{% extends "emails/base_email.html" %}
{% block title %}{{ title }}{% endblock %}
{% block header_extra %}
{% if title %}
<p style="margin:16px 0 0;font-size:18px;font-weight:600;color:#212121;line-height:1.4;">{{ title }}</p>
{% endif %}
{% endblock %}
{% block content %}
{{ content_html|safe }}
{% endblock %}
{% block footer_note %}
{% if prefs_url or one_click_url %}
{% if prefs_url %}
<a href="{{ prefs_url }}" style="color:#00626c;text-decoration:underline;">Manage preferences</a>
{% endif %}
{% if prefs_url and one_click_url %}&nbsp;·&nbsp;{% endif %}
{% if one_click_url %}
<a href="{{ one_click_url }}" style="color:#00626c;text-decoration:underline;">Unsubscribe from email</a>
{% endif %}
{% endif %}
{% endblock %}
@@ -0,0 +1,10 @@
{% if title %}{{ title }}
{% endif %}{{ content }}
{% if prefs_url %}Manage preferences: {{ prefs_url }}
{% endif %}{% if one_click_url %}Unsubscribe from email: {{ one_click_url }}
{% endif %}{{ brand_name|default:"Monica Dhillon" }}
{{ site_url|default:"https://mkdrealtor.com" }}
{% if brand_tagline %}{{ brand_tagline }}{% endif %}