[object Object]

← back to Rentv

TK-10578: pr-intelligence buildMime — add List-Unsubscribe header + physical-address/opt-out footer for CAN-SPAM bulk compliance (opt-in; 1:1 replies unchanged)

f552f260aa5e62ef556271312d3d2b1e3e9a94ef · 2026-08-15 08:19:25 -0700 · Steve Abrams

Files touched

Diff

commit f552f260aa5e62ef556271312d3d2b1e3e9a94ef
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat Aug 15 08:19:25 2026 -0700

    TK-10578: pr-intelligence buildMime — add List-Unsubscribe header + physical-address/opt-out footer for CAN-SPAM bulk compliance (opt-in; 1:1 replies unchanged)
---
 src/pr/services/email-providers.js | 27 ++++++++++++++++++++++-----
 1 file changed, 22 insertions(+), 5 deletions(-)

diff --git a/src/pr/services/email-providers.js b/src/pr/services/email-providers.js
index e5881eb8..1c2d778a 100644
--- a/src/pr/services/email-providers.js
+++ b/src/pr/services/email-providers.js
@@ -19,13 +19,30 @@ const TOKEN_URL = 'https://oauth2.googleapis.com/token';
 
 function b64url(s) { return Buffer.from(s).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); }
 
-function buildMime({ from, to, subject, text, html }) {
+// CAN-SPAM §7704(a)(5): any COMMERCIAL/bulk send must carry a working opt-out + the sender's
+// physical postal address. 1:1 replies pass `unsubscribe` undefined and are unchanged; a
+// bulk/programmatic commercial send MUST pass `unsubscribe` (an https one-click URL or a
+// mailto:) so the List-Unsubscribe header + the address/opt-out footer are injected. (TK-10578)
+const RENTV_POSTAL = process.env.PR_MAILING_ADDRESS || '5757 W. Century Blvd, Suite 700, Los Angeles, CA 90045';
+
+function buildMime({ from, to, subject, text, html, unsubscribe }) {
   const boundary = 'b' + Date.now().toString(36);
+  const headers = [`From: ${from}`, `To: ${to}`, `Subject: ${subject}`, 'MIME-Version: 1.0'];
+  let textBody = text || '';
+  let htmlBody = html || `<pre>${text || ''}</pre>`;
+  if (unsubscribe) {
+    const isUrl = /^https?:/i.test(unsubscribe);
+    headers.push(`List-Unsubscribe: <${unsubscribe}>`);
+    if (isUrl) headers.push('List-Unsubscribe-Post: List-Unsubscribe=One-Click'); // RFC 8058 one-click
+    const optOut = isUrl ? `Unsubscribe: ${unsubscribe}` : `To stop these emails, reply STOP or email ${unsubscribe.replace(/^mailto:/i, '')}.`;
+    textBody += `\r\n\r\n—\r\nRENTV · ${RENTV_POSTAL}\r\n${optOut}`;
+    htmlBody += `<hr><p style="font:12px/1.5 sans-serif;color:#666">RENTV · ${RENTV_POSTAL}<br>${optOut}</p>`;
+  }
+  headers.push(`Content-Type: multipart/alternative; boundary="${boundary}"`, '');
   return [
-    `From: ${from}`, `To: ${to}`, `Subject: ${subject}`, 'MIME-Version: 1.0',
-    `Content-Type: multipart/alternative; boundary="${boundary}"`, '',
-    `--${boundary}`, 'Content-Type: text/plain; charset="UTF-8"', '', text || '', '',
-    `--${boundary}`, 'Content-Type: text/html; charset="UTF-8"', '', html || `<pre>${text || ''}</pre>`, '',
+    ...headers,
+    `--${boundary}`, 'Content-Type: text/plain; charset="UTF-8"', '', textBody, '',
+    `--${boundary}`, 'Content-Type: text/html; charset="UTF-8"', '', htmlBody, '',
     `--${boundary}--`, '',
   ].join('\r\n');
 }

← 9ba22578 auto-data-snapshot: 2026-08-15T08:12:28 (7 data files) — dat  ·  back to Rentv  ·  SAFETY: fail-closed kill switch on PR Gmail send() — Steve d 8de14eef →