📱 BizLeadsConnect
SMS Appointment Reminder System – TCPA Compliant Setup
✅ FULLY TCPA COMPLIANT – Campaign ID: BLC-APPT-2024-001
Campaign Workflow: Complete Step-by-Step Guide
1
Initial Contact via Advertisement
Users respond to advertisements by calling or texting the displayed phone number.
Important: This is considered an “inquiry” – not yet consent for marketing messages.
2
Automated Consent Request (Immediate Response)
System automatically sends this exact message within 30 seconds:
Compliance Note: No further messages sent until consent is received.
3
User Provides Explicit Consent
User must reply with express written consent:
System Requirement: Log timestamp, IP address (if available), exact consent message, and phone number.
4
Confirmation Message with Required Disclosures
Upon receiving consent, system sends:
5
Appointment Reminders (Only After Booking)
Once appointment is booked, reminder messages can be sent:
📋 REQUIRED CONSENT TRACKING (Log These Fields)
- ✓ Phone Number (E.164 format: +1234567890)
- ✓ Timestamp of initial inquiry (UTC)
- ✓ Exact consent message received (YES/I AGREE)
- ✓ Timestamp of consent (UTC)
- ✓ IP Address of opt-in (if web-based)
- ✓ Campaign ID: BLC-APPT-2024-001
- ✓ Consent method: SMS Reply
✅ Compliance Checklist for Approval
- 🔲 Clear opt-in request – Must be separate from any other message
- 🔲 Explicit consent recorded – Cannot use pre-checked boxes or implied consent
- 🔲 Disclosure of program – “You are opting in to receive messages from…”
- 🔲 Message frequency disclosure – How often messages will be sent
- 🔲 Msg & data rates may apply – Exact wording required
- 🔲 STOP/HELP instructions – Clear opt-out mechanism
- 🔲 Privacy policy URL – Must be accessible
- 🔲 Terms & conditions URL – Must be accessible
📊 Database Schema for Consent Tracking
CREATE TABLE consent_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
phone_number VARCHAR(20) NOT NULL,
campaign_id VARCHAR(50) DEFAULT ‘BLC-APPT-2024-001’,
initial_inquiry_time DATETIME NOT NULL,
consent_message_sent_time DATETIME,
user_consent_received VARCHAR(50),
user_consent_time DATETIME,
confirmation_sent_time DATETIME,
opt_out_status BOOLEAN DEFAULT FALSE,
ip_address VARCHAR(45),
user_agent TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
id INT AUTO_INCREMENT PRIMARY KEY,
phone_number VARCHAR(20) NOT NULL,
campaign_id VARCHAR(50) DEFAULT ‘BLC-APPT-2024-001’,
initial_inquiry_time DATETIME NOT NULL,
consent_message_sent_time DATETIME,
user_consent_received VARCHAR(50),
user_consent_time DATETIME,
confirmation_sent_time DATETIME,
opt_out_status BOOLEAN DEFAULT FALSE,
ip_address VARCHAR(45),
user_agent TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
🔧 Sample API Implementation (Node.js)
// Twilio Webhook Handler Example
app.post(‘/webhook/incoming’, async (req, res) => {
const { Body, From } = req.body;
const consentRecord = await ConsentLog.findOne({ phone: From });
// Step 1: New inquiry – no consent yet
if (!consentRecord) {
await ConsentLog.create({ phone: From, status: ‘pending’ });
return sendMessage(From, “We need your permission… Please respond YES or I AGREE”);
}
// Step 2: User responding to consent request
if (consentRecord.status === ‘pending’ &&
(Body.toUpperCase().includes(‘YES’) || Body.toUpperCase().includes(‘AGREE’))) {
await ConsentLog.updateOne({ phone: From }, {
status: ‘opted_in’,
consentTime: new Date(),
consentText: Body
});
return sendMessage(From, “You have opted in… Msg & data rates may apply. Reply STOP to unsubscribe.”);
}
// Step 3: Handle opt-out
if (Body.toUpperCase() === ‘STOP’) {
await ConsentLog.updateOne({ phone: From }, { status: ‘opted_out’ });
return sendMessage(From, “You have been unsubscribed. No further messages will be sent.”);
}
});
🚀 Submit Campaign for Review
app.post(‘/webhook/incoming’, async (req, res) => {
const { Body, From } = req.body;
const consentRecord = await ConsentLog.findOne({ phone: From });
// Step 1: New inquiry – no consent yet
if (!consentRecord) {
await ConsentLog.create({ phone: From, status: ‘pending’ });
return sendMessage(From, “We need your permission… Please respond YES or I AGREE”);
}
// Step 2: User responding to consent request
if (consentRecord.status === ‘pending’ &&
(Body.toUpperCase().includes(‘YES’) || Body.toUpperCase().includes(‘AGREE’))) {
await ConsentLog.updateOne({ phone: From }, {
status: ‘opted_in’,
consentTime: new Date(),
consentText: Body
});
return sendMessage(From, “You have opted in… Msg & data rates may apply. Reply STOP to unsubscribe.”);
}
// Step 3: Handle opt-out
if (Body.toUpperCase() === ‘STOP’) {
await ConsentLog.updateOne({ phone: From }, { status: ‘opted_out’ });
return sendMessage(From, “You have been unsubscribed. No further messages will be sent.”);
}
});