← back to Butlr
always-announce two-party consent preamble — Steve's standing rule 2026-05-12
040cea444a86f1e132ef4c84a92c74267257165b · 2026-05-12 12:50:09 -0700 · Steve
Per ~/.claude/.../memory/feedback_holdforme_always_announce.md:
- buildAnnouncement(call) + announceTwiml(call) in lib/twilio.js
- DRY-RUN now logs [ANNOUNCE] preamble line so the rule is visible pre-Twilio
- LIVE mode will emit <Say>+<Pause> as first TwiML node on every outbound call
- No skip flag; California Cal. Penal Code § 632 protection is uniform
Files touched
Diff
commit 040cea444a86f1e132ef4c84a92c74267257165b
Author: Steve <steve@designerwallcoverings.com>
Date: Tue May 12 12:50:09 2026 -0700
always-announce two-party consent preamble — Steve's standing rule 2026-05-12
Per ~/.claude/.../memory/feedback_holdforme_always_announce.md:
- buildAnnouncement(call) + announceTwiml(call) in lib/twilio.js
- DRY-RUN now logs [ANNOUNCE] preamble line so the rule is visible pre-Twilio
- LIVE mode will emit <Say>+<Pause> as first TwiML node on every outbound call
- No skip flag; California Cal. Penal Code § 632 protection is uniform
---
lib/twilio.js | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/lib/twilio.js b/lib/twilio.js
index 014811d..099c087 100644
--- a/lib/twilio.js
+++ b/lib/twilio.js
@@ -34,6 +34,28 @@ const DRY_RUN = process.env.TWILIO_DRY_RUN !== '0'; // default ON
function log(...args) { console.log('[twilio]', new Date().toISOString(), ...args); }
+// ── ALWAYS-ANNOUNCE PREAMBLE ─────────────────────────────────────────
+// Steve's standing rule (2026-05-12): every outbound call MUST open with
+// a two-party-consent announcement before any IVR navigation or recording.
+// California Cal. Penal Code § 632 + 10 other two-party states. No exceptions.
+// See ~/.claude/projects/-Users-stevestudio2/memory/feedback_holdforme_always_announce.md
+function buildAnnouncement(call) {
+ const onBehalfOf = call.callback_name || 'a HoldForMe customer';
+ return [
+ `Hello. This is an automated assistant calling on behalf of ${onBehalfOf}.`,
+ `This call may be recorded for quality and accuracy.`,
+ `If you do not consent to recording, please stay on the line and a human will return shortly.`,
+ `Otherwise, please continue, and thank you for your time.`,
+ ].join(' ');
+}
+function announceTwiml(call) {
+ const text = buildAnnouncement(call);
+ // Use neutral voice; <Pause/> gives the called party a beat to object.
+ return `<Say voice="Polly.Joanna">${text}</Say><Pause length="2"/>`;
+}
+module.exports.buildAnnouncement = buildAnnouncement;
+module.exports.announceTwiml = announceTwiml;
+
// Dry-run state machine — simulates a real call advancing through stages.
// queued → dialing → on_hold → connected → done
// Each transition takes a few seconds so the UI shows live progression.
@@ -88,6 +110,8 @@ async function dialCall(call) {
log(`DRY-RUN call=${call.id} business="${call.business_name}" phone=${call.business_phone}`);
log(` goal="${call.goal.slice(0, 80)}…"`);
log(` callback=${call.callback_phone} (${call.callback_name})`);
+ log(`[ANNOUNCE] preamble would play here:`);
+ log(` "${buildAnnouncement(call)}"`);
data.updateStatus(call.id, 'dialing');
return { ok: true, dry_run: true };
}
← 38ede4f /social-claim — admin tool that generates a one-page claim s
·
back to Butlr
·
live-audio bridge + recording + Whisper transcript on finish 9846567 →