Send branded HTML emails and harden SMTP2GO webhooks.
Fix campaign stuck on sending under async queue, and stop UUID ValidationError when SMTP2GO tests send "Headers Unavailable".
This commit is contained in:
@@ -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)
|
||||
@@ -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;"> </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;">
|
||||
© {% 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 %}
|
||||
· {{ 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 %} · {% 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 %}
|
||||
Reference in New Issue
Block a user