SMS Marketing Compliance Guide for Developers
Building SMS features into your app? Here's what you need to know about TCPA, 10DLC, A2P registration, and consent management — with code examples to get it right.
Disclaimer: This guide is for informational purposes and is not legal advice. SMS compliance laws vary by jurisdiction and change frequently. Consult with a legal professional for your specific use case.
Why SMS Compliance Matters for Developers
If you're building an app that sends text messages — follow-ups, notifications, marketing, reminders — you are on the hook for compliance. Not your users. Not the carrier. You, the developer who built the system.
The penalties are real. TCPA violations carry fines of $500-$1,500 per message. A single campaign to 1,000 numbers without proper consent could result in a $500,000-$1.5M liability. And carriers will shut down your number without warning if they detect non-compliant traffic.
The good news: compliance is straightforward if you build it into your system from day one.
The Regulatory Landscape
TCPA (Telephone Consumer Protection Act)
The federal law governing SMS marketing in the United States. Key requirements:
- Prior express written consent is required before sending marketing messages
- Consent must be clear and conspicuous — not buried in a terms-of-service page
- Every message must include an opt-out mechanism (reply STOP to unsubscribe)
- You must honor opt-outs immediately — no "processing your request" delays
- Transactional messages (order confirmations, appointment reminders) have different rules than marketing messages
10DLC (10-Digit Long Code) Registration
Since 2024, all carriers (AT&T, T-Mobile, Verizon) require businesses to register their 10-digit phone numbers for A2P (Application-to-Person) messaging. Without registration:
- Messages will be silently filtered (they don't bounce, they just disappear)
- Your throughput is capped at 1 message per second or less
- Carriers may block your number entirely
A2P Registration Process
Here's what you need to register through your provider (Twilio, Vonage, etc.):
- Brand registration — Register your company with The Campaign Registry (TCR)
- Campaign registration — Register each messaging use case (marketing, notifications, etc.)
- Sample messages — Provide example messages for each campaign
- Approval — Wait 1-7 business days for carrier approval
Cost is typically $4/month for brand registration + $10/month per campaign through Twilio or similar.
Building a Compliant Messaging System
Step 1: Consent Collection
Your system must track consent at the individual level. Here's a schema and implementation:
CREATE TABLE sms_consent (
id SERIAL PRIMARY KEY,
phone_number VARCHAR(20) NOT NULL, -- E.164 format: +15125551234
consent_given BOOLEAN DEFAULT FALSE,
consent_type VARCHAR(50), -- 'marketing', 'transactional', 'both'
consent_source VARCHAR(100), -- 'web_form', 'sms_keyword', 'paper'
consent_text TEXT, -- Exact disclosure text shown at opt-in
consented_at TIMESTAMP,
opted_out BOOLEAN DEFAULT FALSE,
opted_out_at TIMESTAMP,
ip_address VARCHAR(45), -- For web form opt-ins
created_at TIMESTAMP DEFAULT NOW()
);
-- Index for fast lookups before sending
CREATE INDEX idx_consent_phone ON sms_consent(phone_number, opted_out);import re
from datetime import datetime
def normalize_phone(phone: str) -> str:
"""Convert any phone format to E.164."""
digits = re.sub(r'\D', '', phone)
if len(digits) == 10:
return f"+1{digits}"
elif len(digits) == 11 and digits[0] == '1':
return f"+{digits}"
return phone # Return as-is if can't normalize
def has_consent(phone: str, message_type: str = "marketing") -> bool:
"""Check if a phone number has valid consent for this message type."""
phone = normalize_phone(phone)
# Query your database
consent = db.query(
"SELECT * FROM sms_consent WHERE phone_number = %s AND opted_out = FALSE",
[phone]
)
if not consent:
return False
# Marketing requires explicit consent
if message_type == "marketing":
return consent.consent_type in ("marketing", "both") and consent.consent_given
# Transactional messages have looser requirements
if message_type == "transactional":
return consent.consent_given
return False
def send_sms(phone: str, message: str, message_type: str = "marketing"):
"""Send an SMS with compliance checks."""
phone = normalize_phone(phone)
# Check consent
if not has_consent(phone, message_type):
raise ValueError(f"No {message_type} consent for {phone}")
# Ensure opt-out language is included in marketing messages
if message_type == "marketing" and "STOP" not in message.upper():
message += "\nReply STOP to unsubscribe"
# Send via your provider (Twilio example)
twilio_client.messages.create(
body=message,
from_=YOUR_TWILIO_NUMBER,
to=phone
)
# Log the send
db.execute(
"INSERT INTO sms_log (phone, message, type, sent_at) VALUES (%s, %s, %s, %s)",
[phone, message, message_type, datetime.utcnow()]
)Step 2: Opt-Out Handling
You must process opt-outs automatically and immediately. The standard keywords are STOP, UNSUBSCRIBE, CANCEL, END, and QUIT:
OPT_OUT_KEYWORDS = {"stop", "unsubscribe", "cancel", "end", "quit"}
OPT_IN_KEYWORDS = {"start", "yes", "unstop"}
def handle_incoming_sms(from_number: str, body: str):
"""Process incoming SMS for opt-in/opt-out keywords."""
normalized = body.strip().lower()
phone = normalize_phone(from_number)
if normalized in OPT_OUT_KEYWORDS:
# Immediately opt out
db.execute(
"""UPDATE sms_consent
SET opted_out = TRUE, opted_out_at = NOW()
WHERE phone_number = %s""",
[phone]
)
# Send confirmation (required by TCPA)
send_raw_sms(phone, "You've been unsubscribed and will no longer receive messages from us.")
return {"action": "opted_out"}
if normalized in OPT_IN_KEYWORDS:
# Re-opt-in
db.execute(
"""UPDATE sms_consent
SET opted_out = FALSE, opted_out_at = NULL
WHERE phone_number = %s""",
[phone]
)
send_raw_sms(phone, "You've been re-subscribed. Reply STOP anytime to opt out.")
return {"action": "opted_in"}
return {"action": "message_received", "body": body}Step 3: Message Content Requirements
Every marketing SMS must include:
- Business identification — The recipient must know who is messaging them
- Opt-out instructions — "Reply STOP to unsubscribe" or similar
- No deceptive content — No fake urgency, no misleading claims
- Sent during appropriate hours — 8 AM to 9 PM in the recipient's local time zone
from datetime import datetime
import pytz
def validate_sms(message: str, business_name: str, recipient_timezone: str = "US/Eastern"):
"""Validate an SMS message for compliance before sending."""
errors = []
# Check message length (SMS limit is 160 chars, or 1600 for long SMS)
if len(message) > 1600:
errors.append("Message exceeds 1600 character limit")
# Check for business identification
if business_name.lower() not in message.lower():
errors.append(f"Message must include business name: {business_name}")
# Check for opt-out language
opt_out_phrases = ["reply stop", "text stop", "opt out", "unsubscribe"]
has_opt_out = any(phrase in message.lower() for phrase in opt_out_phrases)
if not has_opt_out:
errors.append("Marketing messages must include opt-out instructions (e.g., 'Reply STOP to unsubscribe')")
# Check sending hours (8 AM - 9 PM in recipient's timezone)
tz = pytz.timezone(recipient_timezone)
local_hour = datetime.now(tz).hour
if local_hour < 8 or local_hour >= 21:
errors.append(f"Outside sending hours in {recipient_timezone} (current: {local_hour}:00, allowed: 8:00-21:00)")
return {"valid": len(errors) == 0, "errors": errors}Using Rebirth API for Compliant SMS Generation
Rebirth API's SMS Generate endpoint creates industry-aware messages. But it generates the content — compliance (consent, opt-out handling, delivery) is your responsibility. Here's how to use it in a compliant workflow:
import requests
REBIRTH_HEADERS = {
"Authorization": "Bearer rb_live_YOUR_KEY",
"Content-Type": "application/json"
}
def send_compliant_follow_up(phone, industry, context, business_name, timezone="US/Eastern"):
"""Generate and send a compliant follow-up SMS."""
# Step 1: Check consent
if not has_consent(phone, "marketing"):
return {"error": "No consent", "sent": False}
# Step 2: Generate message with Rebirth API
gen_resp = requests.post(
"https://rebirthapi.com/api/v1/sms-generate",
headers=REBIRTH_HEADERS,
json={
"type": "follow_up",
"industry": industry,
"context": context,
"business_name": business_name
}
).json()
message = gen_resp["message"]
# Step 3: Ensure compliance elements are present
if "STOP" not in message.upper():
message += " Reply STOP to opt out."
# Step 4: Validate before sending
validation = validate_sms(message, business_name, timezone)
if not validation["valid"]:
return {"error": validation["errors"], "sent": False}
# Step 5: Send via Twilio
send_sms(phone, message, message_type="marketing")
return {"message": message, "sent": True}
# Usage
result = send_compliant_follow_up(
phone="+15125551234",
industry="hvac",
context="Customer asked about AC repair pricing",
business_name="CoolAir HVAC"
)
print(result["message"])
# "Hey! This is CoolAir HVAC following up on your AC question.
# We're running a spring tune-up special — want a free estimate?
# Reply YES or call (512) 555-0000. Reply STOP to opt out."Compliance Checklist
Use this checklist before launching any SMS feature:
- ☐10DLC Registration — Brand and campaign registered with your SMS provider
- ☐Consent collection — Clear opt-in form with explicit consent language
- ☐Consent storage — Database with timestamp, source, and exact disclosure text
- ☐Opt-out handling — Automatic processing of STOP, UNSUBSCRIBE, CANCEL, END, QUIT
- ☐Opt-out confirmation — Confirmation message sent after opt-out
- ☐Business identification — Every message includes the business name
- ☐Opt-out instructions — Every marketing message includes "Reply STOP to unsubscribe"
- ☐Sending hours — Messages only sent 8 AM-9 PM in recipient's time zone
- ☐Phone number format — All numbers stored and sent in E.164 format (+15125551234)
- ☐Audit log — Every sent message logged with timestamp, content, and recipient
Common Mistakes Developers Make
1. Assuming "They Gave Us Their Number" Is Consent
A customer calling your business is NOT consent to send marketing SMS. Consent must be explicit, in writing, and specific to SMS marketing. A checkbox on a web form that says "I agree to receive promotional text messages from [Business]" qualifies. An implied agreement doesn't.
2. Not Using E.164 Format
Phone numbers should always be stored in E.164 format: +15125551234. Without the country code and proper formatting, messages silently fail or get routed incorrectly. Twilio's error 30034 is usually a formatting issue.
3. Skipping 10DLC Registration
Without 10DLC registration, your messages will be silently filtered by carriers. They won't bounce. They won't error. They just won't arrive. This is the #1 reason developers think "SMS doesn't work" when it's actually a registration issue.
4. Not Handling the STOP Keyword
Most providers (Twilio, Vonage) handle STOP automatically at the platform level. But if you're building custom logic, you must handle it yourself. And you must stop sending within seconds, not days.
5. Sending at 2 AM
If your system runs on UTC and your recipients are in Pacific Time, a 10 AM UTC send arrives at 2 AM PT. Always convert to the recipient's local time zone before sending.
FAQ
Do transactional messages need consent?
Transactional messages (order confirmations, appointment reminders, shipping updates) require consent, but it can be implied through the business relationship. You don't need explicit written consent for these. However, you still need opt-out capability.
How long is consent valid?
There is no hard expiration, but best practice is to re-confirm consent if you haven't messaged someone in 18 months. If someone gave consent 3 years ago and has never heard from you, sending a marketing blast could be a problem.
Can I buy phone number lists?
No. Purchased lists do not constitute consent. Every number you message must have individually opted in to receive messages from your specific business. This is non-negotiable under TCPA.
What about Canada and the EU?
Canada follows CASL (Canadian Anti-Spam Legislation) which is similar but stricter than TCPA. The EU follows GDPR, which requires explicit consent and data processing agreements. If you message internationally, comply with the strictest applicable law.
Generate Compliant SMS Content
Rebirth API's SMS Generate endpoint creates industry-aware messages. Free tier with 100 calls/month.