CleanerHQ — TCPA & SMS Compliance Audit Report
Table of Contents
- Overview — What CleanerHQ Is & How SMS Is Used
- Consent Collection Points (with screenshots)
- Consent Disclosure Language
- Admin Controls — SMS Settings Dashboard
- Opt-Out Handling & Registry
- Send-Time Compliance Checks
- Public Consent Proof Page
- Backend Infrastructure Summary
- Links — Privacy Policy & Terms
1. Overview
CleanerHQ is a vertical SaaS platform for the cleaning industry — a complete Quote-to-Cash operating system. The platform sends transactional SMS notifications to cleaning business customers and staff.
SMS Message Types
| Message Type | Trigger | Template |
|---|---|---|
| Appointment Reminder | Day before scheduled job | job_reminder |
| “On My Way” Alert | Crew starts traveling to job | on_my_way |
| Job Completion | Job marked as completed | job_completed |
| Invoice Payment Reminder | Invoice past due | invoice_reminder |
| Booking Confirmation | Quote converted to job | booking_confirmation |
| Schedule Change | Job rescheduled or cancelled | schedule_change_* |
| Review Request | Post-service follow-up | review_request |
| Running Late | Crew reports delay | running_late |
All messages are transactional (not marketing). Message frequency: typically 2–6 per service visit, up to 20 per month per phone number (hard cap enforced).
2. Consent Collection Points
CleanerHQ collects SMS consent through 4 distinct touchpoints. All require explicit, affirmative opt-in (checkbox unchecked by default). No pre-checked boxes.
2a. Public Booking Widget — SMS Consent Checkbox
When a customer requests a quote through the CleanerHQ booking widget embedded on a cleaning business’s website, they see an SMS consent checkbox that appears only when a phone number is entered. The checkbox is unchecked by default, requiring active opt-in.
Route: /widget/w/[widget-id] (public, no authentication required)
Component: BookingWidgetForm
2b. Client Portal — SMS Consent on Booking & Service Requests
When existing customers book services or submit service requests through the client portal, SMS consent is collected with the same checkbox pattern. The portal uses magic-link authentication (passwordless).
Routes: /portal/booking, /portal/request-service (authenticated portal)
Components: PortalBookingForm, ServiceRequestForm
2c. CRM — Create Contact with SMS Consent
When a cleaning business owner adds a new customer through the CRM, they can capture SMS consent on behalf of the customer (for phone-based or in-person consent). The checkbox appears only when a phone number is entered.
Route: /crm/accounts/[id] → “Add Contact” button (dialog)
Component: AddContactDialog
2d. CRM — Edit Contact SMS Toggle
When editing an existing contact, staff can toggle SMS consent on or off via a Switch control. This allows managing consent status for contacts who gave consent via phone call or in-person.
Route: /crm/accounts/[id] → Edit button on contact row (dialog)
Component: EditContactDialog
2e. Job Forms — SMS Consent Notice
Job creation and editing forms display an informational notice below the phone field, informing staff that SMS notifications will be sent to the number only if the contact has SMS consent enabled.
Routes: /jobs/new, /jobs/[id]/edit
Components: JobForm
3. Consent Disclosure Language
Short Form (used on all consent checkboxes)
Long Form (used on public consent proof page and Terms of Service)
4. Admin Controls — SMS Settings Dashboard
Workspace owners have a dedicated SMS settings page at /settings/sms with 4 management sections:
| Section | Purpose |
|---|---|
| SMS Feature Status | Shows whether SMS is enabled/disabled for the workspace. Links to Company Settings to toggle. |
| Monthly SMS Budget | Spend vs. limit with progress bar. Alert when approaching threshold. Default: $50/month. |
| SMS Consent Language | Read-only preview of TCPA-compliant consent text. Cannot be modified (regulatory requirement). |
| Opt-Out Registry | Table of all phone numbers that have opted out via STOP keyword. Read-only. |
5. Opt-Out Handling
Keyword Processing
All inbound SMS messages are processed by the Twilio webhook at /api/webhooks/twilio/inbound with HMAC-SHA1 signature validation.
| Keyword(s) | Action | Auto-Reply |
|---|---|---|
STOP, END, QUIT, UNSUBSCRIBE | Opt-out: set sms_opt_outs.opted_out = true, revoke contacts.sms_consent for all matching contacts | “You have been unsubscribed from CleanerHQ SMS notifications. You will not receive any more messages. Reply START to re-subscribe.” |
START, YES, SUBSCRIBE | Opt-in: set sms_opt_outs.opted_out = false | “You have been re-subscribed to CleanerHQ SMS notifications. Reply STOP to opt out at any time.” |
HELP, INFO | No state change | “CleanerHQ SMS notifications for your cleaning appointments. Reply STOP to opt out. For support, visit cleanerhq.com or email support@cleanerhq.com.” |
All keywords are processed case-insensitively. Opt-outs are honored immediately — the opted-out phone number is checked before every SMS send.
6. Send-Time Compliance Checks
Every SMS message passes through a compliance pipeline before being sent. Implemented in lib/notifications/sms-sender.ts and lib/sms/twilio.ts.
| Check | Rule | File |
|---|---|---|
| Consent Required | contacts.sms_consent === true | sms-sender.ts |
| Opt-Out Honored | Check sms_opt_outs.opted_out before every send | sms-sender.ts |
| Time Restrictions | 8:00 AM – 9:00 PM in recipient’s local timezone | time-restrictions.ts |
| STOP Footer | “Reply STOP to opt out” appended to every message | message-footer.ts |
| Per-Phone Cap | Max 20 messages per phone number per month | twilio.ts |
| Workspace Budget | Checked via checkSMSBudget() RPC, default $50/month | budget.ts |
| Budget Alert | Alert sent when spend reaches threshold (default 80%) | budget.ts |
sms_usage_log
7. Public SMS Consent Proof Page
A publicly accessible page at /sms-consent (no login required) documents all SMS consent collection flows for Twilio A2P 10DLC reviewers.
URL: https://app.cleanerhq.com/sms-consent
Authentication: None required (publicly accessible)
Purpose: Twilio A2P 10DLC compliance reviewer verification
8. Backend Infrastructure Summary
Database Tables
| Table | Purpose | Key Columns |
|---|---|---|
contacts | Contact-level consent tracking | sms_consent, sms_consent_date, sms_consent_method, sms_consent_ip_address |
sms_opt_outs | Phone-level opt-out registry | phone_number, opted_out, opted_out_at, last_message_received |
sms_budgets | Workspace monthly budget | monthly_limit_cents, current_month_spend_cents, alert_threshold_percent |
sms_usage_log | Per-message audit trail | phone_number, direction, message_sid, template_id, cost_cents |
Server Actions & API Routes
| Component | File | Status |
|---|---|---|
| SMS sending (Twilio direct HTTP) | lib/sms/twilio.ts | Complete |
| 16 SMS templates | lib/sms/templates.ts | Complete |
| STOP footer on all messages | lib/sms/message-footer.ts | Complete |
| TCPA 8AM–9PM time restrictions | lib/sms/time-restrictions.ts | Complete |
| Workspace SMS budget & caps | lib/sms/budget.ts | Complete |
| Phone number normalization | lib/sms/utils.ts | Complete |
| Opt-out keyword handling | api/webhooks/twilio/inbound/route.ts | Complete |
| Delivery status webhook | api/webhooks/twilio/status/route.ts | Complete |
| Consent grant/revoke/check | app/actions/sms-consent.ts | Complete |
| Consent check before every send | lib/notifications/sms-sender.ts | Complete |
| Per-phone monthly cap (20 msgs) | lib/sms/twilio.ts | Complete |
9. Links
| Resource | URL |
|---|---|
| Privacy Policy (Section 6: SMS Privacy) | https://cleanerhq.com/privacy-policy/ |
| Terms & Conditions (Section 4: SMS Terms) | https://cleanerhq.com/terms-and-conditions/ |
| Public SMS Consent Proof Page | https://app.cleanerhq.com/sms-consent |
| Support Email | support@cleanerhq.com |
| Website | https://cleanerhq.com |