Compliance & Regulations
Understand and implement compliance requirements for messaging regulations worldwide.
GDPR (Europe)
Data Processing
- Obtain explicit consent before messaging
- Provide opt-out mechanism
- Honor data deletion requests
- Maintain processing records
Implementation
// Check consent before sending
async function canMessageUser(userId: string) {
const user = await db.users.findById(userId);
return user.messagingConsent === true &&
user.optedOutAt === null;
}
// Handle opt-out
app.post('/opt-out', async (req, res) => {
await db.users.update(req.body.phoneNumber, {
optedOutAt: new Date(),
messagingConsent: false
});
res.send('You have been unsubscribed');
});TCPA (United States)
Requirements
- Written consent for marketing messages
- Honor opt-outs within 24 hours
- Maintain consent records
- No messages before 8 AM or after 9 PM (recipient's timezone)
Best Practices
// Timezone-aware sending
async function sendInUserTimezone(userId: string, templateId: string) {
const user = await db.users.findById(userId);
const userTime = new Date().toLocaleString('en-US', {
timeZone: user.timezone
});
const hour = new Date(userTime).getHours();
// Only send between 9 AM and 8 PM
if (hour >= 9 && hour < 20) {
return client.messages.send({
to: [user.phoneNumber],
template: { id: templateId }
});
} else {
// Queue for next available window
await queueForTomorrow(userId, templateId);
}
}Country-Specific Requirements
| Country | Requirement |
|---|---|
| Australia | SPAM Act compliance, unsubscribe within 5 days |
| Canada | CASL - express consent required |
| UK | PECR regulations, opt-in required |
| Singapore | PDPA compliance |
| Brazil | LGPD data protection |
RCS Compliance
RCS has built-in compliance features that simplify opt-out management:
- STOP chip auto-injection: Every RCS message automatically includes a STOP suggestion chip. You do not need to add opt-out footer text manually — the platform handles this for you.
- Inbound replies: When a user taps the STOP chip or sends an opt-out keyword, the platform updates the contact's
opt_outflag and fires amessage.receivedwebhook with the user's text. Treat any inbound opt-out keyword (STOP / UNSUBSCRIBE / END / CANCEL / QUIT) the same way across channels.
const OPT_OUT_KEYWORDS = new Set(['STOP', 'UNSUBSCRIBE', 'END', 'CANCEL', 'QUIT']);
app.post('/webhooks/sent', async (req, res) => {
res.sendStatus(200);
const { field, sub_type, payload } = req.body;
if (field === 'message' && sub_type === 'message.received') {
const keyword = (payload.text ?? '').trim().toUpperCase();
if (OPT_OUT_KEYWORDS.has(keyword)) {
// The platform has already flipped the contact's opt_out flag — mirror it locally.
await db.users.update(payload.from, {
optedOutAt: new Date(),
messagingConsent: false
});
}
}
});Opt-Out Handling at Send-Time
Sent runs a pre-send consent gate on every message. If the recipient's contact has opt_out = true, or their phone is on your phone-channel suppression list, the message is finalized as FAILED with the ERR_CONSENT_BLOCKED reason — no provider call is made and you are not charged.
When every recipient on a POST /v3/messages batch is opted out, the API rejects the request synchronously with BUSINESS_004. For partial opt-outs the request succeeds and only the blocked messages fail asynchronously.
app.post('/webhooks/sent', async (req, res) => {
// Acknowledge immediately
res.sendStatus(200);
const { field, payload } = req.body;
if (field === 'message' && payload.message_status === 'FAILED') {
// Fetch the message detail to read the failure reason (description includes the error code,
// e.g. ERR_CONSENT_BLOCKED for an opt-out / suppression block).
const message = await client.messages.retrieveStatus(payload.message_id);
const description = message.data?.description ?? '';
if (description.includes('ERR_CONSENT_BLOCKED')) {
await db.users.update(payload.inbound_number, {
optedOutAt: new Date(),
messagingConsent: false
});
}
}
});