← 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
M src/pr/services/email-providers.js
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 →