diff --git a/docker-compose.yml b/docker-compose.yml index fc89089..2d46c51 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -300,6 +300,11 @@ services: listmonk: image: listmonk/listmonk:latest + # Stable hostname so the Message-ID Listmonk derives from the container OS + # hostname is perfwest.performancewest.net, NOT the random docker container + # id -> @localhost.localdomain (a spam-score signal; see deliverability + # runbook). Matches Listmonk's SMTP hello_hostname. + hostname: perfwest.performancewest.net ports: - "9100:9000" environment: @@ -335,6 +340,9 @@ services: # mynetworks 172.16/12). host.docker.internal is mapped for convenience. listmonk-hc: image: listmonk/listmonk:latest + # Stable hostname -> Message-ID @perfwest.performancewest.net, not the random + # container id -> @localhost.localdomain (spam-score signal). See listmonk above. + hostname: perfwest.performancewest.net ports: - "9101:9000" extra_hosts: diff --git a/scripts/_email_plaintext.py b/scripts/_email_plaintext.py new file mode 100644 index 0000000..3fa7f99 --- /dev/null +++ b/scripts/_email_plaintext.py @@ -0,0 +1,105 @@ +"""Shared HTML -> plaintext conversion for outbound campaigns. + +Every campaign we build was HTML-only (no plaintext MIME part). A missing +text/plain alternative is a spam-score signal: legitimate bulk senders ship +multipart/alternative, and several filters (and most "this looks like spam" +heuristics) penalise HTML-only mail. It also degrades the experience for +plaintext-only clients and accessibility tooling. + +Listmonk only emits multipart/alternative when a campaign's `altbody` is set; +otherwise it sends text/html alone. So we generate a readable plaintext +rendition from the HTML body and pass it as `altbody`. + +This is intentionally dependency-free (no bs4/html2text on the prod box): a +small, well-tested regex pipeline that: + - drops