fix(email): add text/plain part to every transactional + telecom email
All transactional/worker senders built multipart/alternative (or mixed) messages with ONLY an HTML part. A single-part multipart/alternative is malformed and HTML-only mail is a spam-score signal -- the same class of deliverability bug that hurt the campaign pipeline, but on the telecom / filing / customer-transactional path (499-Q reminders, RMD/FCC filing review links, intake/completion/delivery emails, commissions, etc). - worker_email.send_worker_email: auto-derive plaintext from HTML when caller omits text= (fixes the shared helper for all current+future use) - 16 rolled-their-own senders in scripts/workers/** + scripts/formation/ document_delivery.py: attach html_to_text(...) plaintext sibling before the HTML part (job_server + document_delivery wrap text+html in an alternative sub-part so PDFs still attach to the mixed root) - api/src/email.ts: add dependency-free htmlToText() and default sendEmail text to it (fixes checkout/webhook HTML-only sends) Verified: all py files compile + import at runtime, api tsc passes, htmlToText handles hrefs/lists/entities, 11 plaintext unit tests pass. Telecom campaign 407 (Jun 8) was HTML-only + sent in the DKIM-broken window -> 384 sent / 0 clicks (same junked-mail signature).
This commit is contained in:
parent
899b880e7f
commit
b375385efd
19 changed files with 114 additions and 8 deletions
|
|
@ -282,6 +282,8 @@ class BaseServiceHandler(ABC):
|
|||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
|
||||
from scripts._email_plaintext import html_to_text
|
||||
|
||||
first_name = customer_name.split(" ")[0] if customer_name else "there"
|
||||
subject = f"Action Required — Complete your {service_label} intake"
|
||||
body = (
|
||||
|
|
@ -308,6 +310,7 @@ class BaseServiceHandler(ABC):
|
|||
msg["From"] = smtp_from
|
||||
msg["To"] = customer_email
|
||||
msg["Reply-To"] = "info@performancewest.net"
|
||||
msg.attach(MIMEText(html_to_text(body), "plain"))
|
||||
msg.attach(MIMEText(body, "html"))
|
||||
with smtplib.SMTP(smtp_host, smtp_port) as server:
|
||||
server.starttls()
|
||||
|
|
|
|||
|
|
@ -282,6 +282,8 @@ class FCCCarrierRegistrationHandler:
|
|||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
|
||||
from scripts._email_plaintext import html_to_text
|
||||
|
||||
email = order.get("customer_email", "")
|
||||
name = order.get("customer_name", "")
|
||||
if not email:
|
||||
|
|
@ -302,6 +304,7 @@ class FCCCarrierRegistrationHandler:
|
|||
msg["Subject"] = subject
|
||||
msg["From"] = os.environ.get("SMTP_FROM", "Performance West <noreply@performancewest.net>")
|
||||
msg["To"] = email
|
||||
msg.attach(MIMEText(html_to_text(body), "plain"))
|
||||
msg.attach(MIMEText(body, "html"))
|
||||
|
||||
smtp_host = os.environ.get("SMTP_HOST", "co.carrierone.com")
|
||||
|
|
|
|||
|
|
@ -241,6 +241,8 @@ class Form499ADiscontinuanceHandler(BaseServiceHandler):
|
|||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
from scripts._email_plaintext import html_to_text
|
||||
|
||||
subject = f"Form 499-A Discontinuance Filed — {entity_name}"
|
||||
html = f"""
|
||||
<div style="font-family:Inter,sans-serif;max-width:600px;margin:0 auto;color:#1f2937">
|
||||
|
|
@ -275,6 +277,7 @@ class Form499ADiscontinuanceHandler(BaseServiceHandler):
|
|||
msg["From"] = os.environ.get("SMTP_FROM", "Performance West <noreply@performancewest.net>")
|
||||
msg["To"] = to
|
||||
msg["Subject"] = subject
|
||||
msg.attach(MIMEText(html_to_text(html), "plain"))
|
||||
msg.attach(MIMEText(html, "html"))
|
||||
|
||||
with smtplib.SMTP(
|
||||
|
|
|
|||
|
|
@ -338,10 +338,13 @@ class Form499QHandler(BaseServiceHandler):
|
|||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
from scripts._email_plaintext import html_to_text
|
||||
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg["From"] = os.environ.get("SMTP_FROM", "Performance West <noreply@performancewest.net>")
|
||||
msg["To"] = to
|
||||
msg["Subject"] = subject
|
||||
msg.attach(MIMEText(html_to_text(html), "plain"))
|
||||
msg.attach(MIMEText(html, "html"))
|
||||
|
||||
with smtplib.SMTP(
|
||||
|
|
|
|||
|
|
@ -136,6 +136,8 @@ class RMDFilingHandler(BaseServiceHandler):
|
|||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
|
||||
from scripts._email_plaintext import html_to_text
|
||||
subject = f"Action Required — Review your RMD certification for {entity.get('legal_name', order_number)}"
|
||||
body = (
|
||||
f"<h2>Your RMD Certification is Ready for Review</h2>"
|
||||
|
|
@ -161,6 +163,7 @@ class RMDFilingHandler(BaseServiceHandler):
|
|||
msg["From"] = smtp_from
|
||||
msg["To"] = customer_email
|
||||
msg["Reply-To"] = "info@performancewest.net"
|
||||
msg.attach(MIMEText(html_to_text(body), "plain"))
|
||||
msg.attach(MIMEText(body, "html"))
|
||||
with smtplib.SMTP(smtp_host, smtp_port) as server:
|
||||
server.starttls()
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ import smtplib
|
|||
from dataclasses import dataclass
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
from scripts._email_plaintext import html_to_text
|
||||
from typing import Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -279,6 +281,7 @@ def _send_admin_email(
|
|||
msg["Subject"] = f"[Review & File] {order_number} — {service_name}"
|
||||
msg["From"] = smtp_from
|
||||
msg["To"] = to_email
|
||||
msg.attach(MIMEText(html_to_text(html), "plain"))
|
||||
msg.attach(MIMEText(html, "html"))
|
||||
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ import smtplib
|
|||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
from scripts._email_plaintext import html_to_text
|
||||
|
||||
logger = logging.getLogger("esign_helper")
|
||||
|
||||
|
||||
|
|
@ -197,6 +199,7 @@ def _send_signing_email(
|
|||
msg["To"] = f"{to_name} <{to_email}>"
|
||||
msg["Subject"] = subject
|
||||
msg["Reply-To"] = "info@performancewest.net"
|
||||
msg.attach(MIMEText(html_to_text(body), "plain"))
|
||||
msg.attach(MIMEText(body, "html"))
|
||||
|
||||
with smtplib.SMTP(smtp_host, smtp_port, timeout=30) as server:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue