name: twilio-communications description: “使用Twilio构建通信功能:短信消息、语音通话、WhatsApp商业API和用户验证(2FA)。涵盖从简单通知到复杂IVR系统和多通道认证的全方位应用。关键关注合规性、速率限制和错误处理。使用场景:twilio、发送短信、文本消息、语音通话、电话验证。” source: vibeship-spawner-skills (Apache 2.0)
Twilio通信
模式
短信发送模式
使用Twilio发送短信的基本模式。 处理基础事项:电话号码格式化、消息传递和传递状态回调。
关键考虑:
- 电话号码必须为E.164格式(+1234567890)
- 默认速率限制:每秒80条消息(MPS)
- 超过160字符的消息会被分割(成本更高)
- 运营商过滤可能屏蔽消息(尤其是美国号码)
使用时机:[‘向用户发送通知’, ‘事务性消息(订单确认、发货)’, ‘警报和提醒’]
from twilio.rest import Client
from twilio.base.exceptions import TwilioRestException
import os
import re
class TwilioSMS:
"""
短信发送,带有适当的错误处理和验证。
"""
def __init__(self):
self.client = Client(
os.environ["TWILIO_ACCOUNT_SID"],
os.environ["TWILIO_AUTH_TOKEN"]
)
self.from_number = os.environ["TWILIO_PHONE_NUMBER"]
def validate_e164(self, phone: str) -> bool:
"""验证电话号码是否为E.164格式。"""
pattern = r'^\+[1-9]\d{1,14}$'
return bool(re.match(pattern, phone))
def send_sms(
self,
to: str,
body: str,
status_callback: str = None
) -> dict:
"""
发送短信消息。
参数:
to: 收件人电话号码,E.164格式
body: 消息文本(160字符=1个分段)
status_callback: 传递状态Webhook的URL
返回:
消息SID和状态
"""
# 验证电话号码格式
if not self.validate_e164(to):
return {
"success": False,
"error": "电话号码必须为E.164格式(+1234567890)"
}
# 检查消息长度(警告分段)
segment_count = (len(body) + 159) // 160
if segment_count > 1:
print(f"警告:消息将作为{segment_count}个分段发送")
try:
message = self.client.messages.create(
to=to,
from_=self.from_number,
body=body,
status_callback=status_callback
)
return {
"success": True,
"message_sid": message.sid,
"status": message.status,
"segments": segment_count
}
except TwilioRestException as e:
return self._handle_error(e)
def _handle_error(self, error: Twilio
Twilio验证模式(2FA/OTP)
使用Twilio验证进行电话号码验证和2FA。 处理代码生成、传递、速率限制和防欺诈。
相比自制OTP的优势:
- Twilio管理代码生成和过期
- 内置防欺诈(已为客户节省8200万+美元,阻止7.47亿次尝试)
- 自动处理速率限制
- 多通道:短信、语音、邮件、推送、WhatsApp
Google发现短信2FA阻止了“100%的自动化机器人、96%的批量网络钓鱼攻击和76%的目标攻击。”
使用时机:[‘用户注册时的电话号码验证’, ‘双因素认证(2FA)’, ‘密码重置验证’, ‘高价值交易确认’]
from twilio.rest import Client
from twilio.base.exceptions import TwilioRestException
import os
from enum import Enum
from typing import Optional
class VerifyChannel(Enum):
SMS = "sms"
CALL = "call"
EMAIL = "email"
WHATSAPP = "whatsapp"
class TwilioVerify:
"""
使用Twilio验证进行电话验证。
永远不要存储OTP代码——Twilio处理它。
"""
def __init__(self, verify_service_sid: str = None):
self.client = Client(
os.environ["TWILIO_ACCOUNT_SID"],
os.environ["TWILIO_AUTH_TOKEN"]
)
# 首先在Twilio控制台中创建验证服务
self.service_sid = verify_service_sid or os.environ["TWILIO_VERIFY_SID"]
def send_verification(
self,
to: str,
channel: VerifyChannel = VerifyChannel.SMS,
locale: str = "en"
) -> dict:
"""
向电话/邮箱发送验证码。
参数:
to: 电话号码(E.164)或邮箱
channel: 短信、通话、邮件或whatsapp
locale: 消息的语言代码
返回:
验证状态
"""
try:
verification = self.client.verify \
.v2 \
.services(self.service_sid) \
.verifications \
.create(
to=to,
channel=channel.value,
locale=locale
)
return {
"success": True,
"status": verification.status, # "pending"
"channel": channel.value,
"valid": verification.valid
}
except TwilioRestException as e:
return self._handle_verify_error(e)
def check_verification(self, to: str, code: str) -> dict:
"""
检查验证码是否正确。
参数:
to: 接收代码的电话号码或邮箱
code: 用户输入的代码
R
TwiML IVR模式
使用TwiML构建交互式语音响应(IVR)系统。 TwiML(Twilio标记语言)是XML,告诉Twilio在接收通话时该做什么。
核心TwiML动词:
- <Say>: 文本转语音
- <Play>: 播放音频文件
- <Gather>: 收集键盘/语音输入
- <Dial>: 连接到另一个号码
- <Record>: 记录呼叫者语音
- <Redirect>: 重定向到另一个TwiML端点
关键洞察:Twilio向您的Webhook发出HTTP请求,您返回TwiML,Twilio执行它。无状态,因此使用URL参数或会话。
使用时机:[‘电话菜单系统(按1联系销售…)’, ‘自动化客户支持’, ‘带有确认的预约提醒’, ‘语音邮件系统’]
from flask import Flask, request, Response
from twilio.twiml.voice_response import VoiceResponse, Gather
from twilio.request_validator import RequestValidator
import os
app = Flask(__name__)
def validate_twilio_request(f):
"""装饰器,验证请求是否来自Twilio。"""
def wrapper(*args, **kwargs):
validator = RequestValidator(os.environ["TWILIO_AUTH_TOKEN"])
# 获取请求详情
url = request.url
params = request.form.to_dict()
signature = request.headers.get("X-Twilio-Signature", "")
if not validator.validate(url, params, signature):
return "无效请求", 403
return f(*args, **kwargs)
wrapper.__name__ = f.__name__
return wrapper
@app.route("/voice/incoming", methods=["POST"])
@validate_twilio_request
def incoming_call():
"""处理来电,带有IVR菜单。"""
response = VoiceResponse()
# 收集数字,带超时
gather = Gather(
num_digits=1,
action="/voice/menu-selection",
method="POST",
timeout=5
)
gather.say(
"欢迎来到Acme公司。"
"按1键联系销售。"
"按2键联系支持。"
"按3键留言。"
)
response.append(gather)
# 如果没有输入,重复
response.redirect("/voice/incoming")
return Response(str(response), mimetype="text/xml")
@app.route("/voice/menu-selection", methods=["POST"])
@validate_twilio_request
def menu_selection():
"""基于菜单选择路由。"""
response = VoiceResponse()
digit = request.form.get("Digits", "")
if digit == "1":
# 转接至销售
response.say("正在为您连接销售。")
response.dial(os.environ["SALES_PHONE"])
elif digit == "2":
# 转接至支持
response.say("正在为您连接支持。")
response.dial(os.environ["SUPPORT_PHONE"])
elif digit == "3":
# 语音邮件
response.say("请在哔声后留言,
⚠️ 注意事项
| 问题 | 严重性 | 解决方案 |
|---|---|---|
| 问题 | 高 | ## 在数据库中跟踪退出状态 |
| 问题 | 中 | ## 为临时故障实施重试逻辑 |
| 问题 | 高 | ## 注册A2P 10DLC(美国要求) |
| 问题 | 严重 | ## 始终验证签名 |
| 问题 | 高 | ## 跟踪每个用户的会话窗口 |
| 问题 | 严重 | ## 切勿硬编码凭据 |
| 问题 | 中 | ## 也实施应用级速率限制 |