第一步:在 Twilio Console 中启用 Advanced Opt-Out
操作步骤:
-
登录 Twilio Console
-
左侧导航点击 Messaging > Services(前提是你使用 Messaging Service。若用单个号码,则点击 Phone Numbers)
-
选择你的 Messaging Service
-
点击 “Opt-out Management” 或 “Compliance” 标签页
-
找到 Advanced Opt-Out 设置
-
启用它,填写以下内容:
自定义内容填写建议:
-
Opt-out keywords: STOP, CANCEL, UNSUBSCRIBE, QUIT
-
Opt-out message:
You have been unsubscribed from notifications. Reply START to re-subscribe.
-
Re-subscribe keywords: START, UNSTOP
-
Re-subscribe message:
You have been re-subscribed to notifications.
-
开启 “Forward opt-out and opt-in messages to your webhook”
-
然后保存设置 ✅
第二步:Webhook 服务端处理 STOP/START 消息
Twilio 会将短信内容(包括 STOP/START)发送到你配置的 Webhook URL。你可以根据关键词记录退订或重新订阅。
数据库设计(MySQL 示例)
CREATE TABLE sms_unsubscribe (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
phone_number VARCHAR(20) NOT NULL UNIQUE,
unsubscribed BOOLEAN NOT NULL DEFAULT TRUE,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
Java(Spring Boot)Webhook 示例代码
@PostMapping("/sms/webhook")
public ResponseEntity<String> receiveSms(@RequestParam("From") String from,
@RequestParam("Body") String body) {
String phone = from.trim();
String content = body.trim().toUpperCase();
if (List.of("STOP", "CANCEL", "UNSUBSCRIBE", "QUIT").contains(content)) {
smsService.unsubscribe(phone);
} else if (List.of("START", "UNSTOP").contains(content)) {
smsService.resubscribe(phone);
}
// Twilio 需要返回 TwiML,即使什么也不发
return ResponseEntity.ok("<Response></Response>");
}
Service 逻辑示例
@Service
public class SmsService {
@Autowired
private SmsUnsubscribeRepository repo;
public void unsubscribe(String phone) {
repo.save(new SmsUnsubscribe(phone, true));
}
public void resubscribe(String phone) {
repo.save(new SmsUnsubscribe(phone, false));
}
}
Postman 测试案例
URL: POST http://your-server.com/sms/webhook
Content-Type: application/x-www-form-urlencoded
Body 示例:
第三步(可选):阻止发送短信给退订用户
在你发短信前检查用户是否退订:
public boolean canSendMessage(String phone) {
return !repo.findByPhoneNumber(phone)
.map(SmsUnsubscribe::isUnsubscribed)
.orElse(false);
}