")
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")
diff --git a/scripts/workers/services/form_499a_discontinuance.py b/scripts/workers/services/form_499a_discontinuance.py
index 2d4320c..9feb44a 100644
--- a/scripts/workers/services/form_499a_discontinuance.py
+++ b/scripts/workers/services/form_499a_discontinuance.py
@@ -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"""
@@ -275,6 +277,7 @@ class Form499ADiscontinuanceHandler(BaseServiceHandler):
msg["From"] = os.environ.get("SMTP_FROM", "Performance West ")
msg["To"] = to
msg["Subject"] = subject
+ msg.attach(MIMEText(html_to_text(html), "plain"))
msg.attach(MIMEText(html, "html"))
with smtplib.SMTP(
diff --git a/scripts/workers/services/form_499q.py b/scripts/workers/services/form_499q.py
index 90b7326..4e83d9a 100644
--- a/scripts/workers/services/form_499q.py
+++ b/scripts/workers/services/form_499q.py
@@ -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 ")
msg["To"] = to
msg["Subject"] = subject
+ msg.attach(MIMEText(html_to_text(html), "plain"))
msg.attach(MIMEText(html, "html"))
with smtplib.SMTP(
diff --git a/scripts/workers/services/rmd_filing.py b/scripts/workers/services/rmd_filing.py
index f0f13cf..c278093 100644
--- a/scripts/workers/services/rmd_filing.py
+++ b/scripts/workers/services/rmd_filing.py
@@ -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"Your RMD Certification is Ready for Review
"
@@ -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()
diff --git a/scripts/workers/services/telecom/auto_filing.py b/scripts/workers/services/telecom/auto_filing.py
index f6145de..4acb9fc 100644
--- a/scripts/workers/services/telecom/auto_filing.py
+++ b/scripts/workers/services/telecom/auto_filing.py
@@ -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:
diff --git a/scripts/workers/services/telecom/esign_helper.py b/scripts/workers/services/telecom/esign_helper.py
index eda6b6a..5b3a1b4 100644
--- a/scripts/workers/services/telecom/esign_helper.py
+++ b/scripts/workers/services/telecom/esign_helper.py
@@ -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:
diff --git a/scripts/workers/usf_factor_monitor.py b/scripts/workers/usf_factor_monitor.py
index 4d7e33b..db93869 100644
--- a/scripts/workers/usf_factor_monitor.py
+++ b/scripts/workers/usf_factor_monitor.py
@@ -52,6 +52,8 @@ from datetime import date, datetime
from decimal import Decimal, InvalidOperation
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
+
+from scripts._email_plaintext import html_to_text
from typing import Iterable
import psycopg2
@@ -604,6 +606,7 @@ def send_email(*, to_email: str, subject: str, html_body: str, dry_run: bool) ->
msg["From"] = FROM_EMAIL
msg["To"] = to_email
msg["Bcc"] = ADMIN_EMAIL
+ msg.attach(MIMEText(html_to_text(html_body), "plain"))
msg.attach(MIMEText(html_body, "html"))
try:
with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
diff --git a/scripts/workers/worker_email.py b/scripts/workers/worker_email.py
index 58cdbc4..ad5d629 100644
--- a/scripts/workers/worker_email.py
+++ b/scripts/workers/worker_email.py
@@ -17,10 +17,21 @@ from __future__ import annotations
import logging
import os
import smtplib
+import sys
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
+# Shared HTML -> plaintext converter (scripts/_email_plaintext.py). Adding a
+# text/plain part to every message is a deliverability win: a multipart/
+# alternative with ONLY an HTML part is malformed, and HTML-only mail is a
+# spam-score signal. Import is path-tolerant (module run from /app or scripts/).
+try:
+ from scripts._email_plaintext import html_to_text
+except ImportError: # pragma: no cover - fallback when scripts/ is on sys.path
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+ from _email_plaintext import html_to_text # type: ignore
+
LOG = logging.getLogger("workers.email")
SMTP_HOST = os.getenv("SMTP_HOST", "co.carrierone.com")
@@ -53,8 +64,13 @@ def send_worker_email(
msg["Subject"] = subject
alt = MIMEMultipart("alternative")
- if text:
- alt.attach(MIMEText(text, "plain"))
+ # Always include a text/plain part. If the caller did not supply one,
+ # derive it from the HTML so the message is a well-formed multipart/
+ # alternative (HTML-only is malformed + a spam signal). RFC 2046: list
+ # the plain part first (least->most preferred).
+ plain = text if text else html_to_text(html)
+ if plain:
+ alt.attach(MIMEText(plain, "plain"))
alt.attach(MIMEText(html, "html"))
msg.attach(alt)